From 1d6b5cdecec6b27b64a4e5ac9317332ec813a4ae Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sun, 7 Jul 2019 10:07:45 +0530 Subject: [PATCH 001/203] feat: calendar initial bringup --- .../doctype/google_calendar/__init__.py | 0 .../google_calendar/google_calendar.js | 8 + .../google_calendar/google_calendar.json | 145 ++++++++++++++++++ .../google_calendar/google_calendar.py | 131 ++++++++++++++++ .../google_calendar/test_google_calendar.py | 10 ++ 5 files changed, 294 insertions(+) create mode 100644 frappe/integrations/doctype/google_calendar/__init__.py create mode 100644 frappe/integrations/doctype/google_calendar/google_calendar.js create mode 100644 frappe/integrations/doctype/google_calendar/google_calendar.json create mode 100644 frappe/integrations/doctype/google_calendar/google_calendar.py create mode 100644 frappe/integrations/doctype/google_calendar/test_google_calendar.py diff --git a/frappe/integrations/doctype/google_calendar/__init__.py b/frappe/integrations/doctype/google_calendar/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.js b/frappe/integrations/doctype/google_calendar/google_calendar.js new file mode 100644 index 0000000000..a5af22a244 --- /dev/null +++ b/frappe/integrations/doctype/google_calendar/google_calendar.js @@ -0,0 +1,8 @@ +// Copyright (c) 2019, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Google Calendar', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.json b/frappe/integrations/doctype/google_calendar/google_calendar.json new file mode 100644 index 0000000000..8cd7608f67 --- /dev/null +++ b/frappe/integrations/doctype/google_calendar/google_calendar.json @@ -0,0 +1,145 @@ +{ + "autoname": "field:user", + "creation": "2019-07-06 17:54:09.450100", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "enable", + "sb_00", + "user", + "calendar_name", + "section_break_3", + "allow_google_access", + "section_break_4", + "refresh_token", + "authorization_code", + "column_break_6", + "session_token", + "state", + "section_break_9", + "gcalendar_id", + "next_sync_token" + ], + "fields": [ + { + "default": "1", + "fieldname": "enable", + "fieldtype": "Check", + "label": "Enable" + }, + { + "depends_on": "eval: doc.enable", + "fieldname": "sb_00", + "fieldtype": "Section Break", + "label": "Google Calendar" + }, + { + "fieldname": "user", + "fieldtype": "Link", + "in_list_view": 1, + "label": "User", + "options": "User", + "reqd": 1, + "unique": 1 + }, + { + "description": "The name that will appear in Google Calendar", + "fieldname": "calendar_name", + "fieldtype": "Data", + "label": "Calendar Name", + "reqd": 1 + }, + { + "depends_on": "eval:doc.enabled", + "fieldname": "section_break_3", + "fieldtype": "Section Break" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "allow_google_access", + "fieldtype": "Button", + "label": "Allow GCalendar Access" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "hidden": 1 + }, + { + "fieldname": "refresh_token", + "fieldtype": "Data", + "label": "Refresh Token" + }, + { + "fieldname": "authorization_code", + "fieldtype": "Data", + "label": "Authorization Code" + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fieldname": "session_token", + "fieldtype": "Data", + "label": "Session Token" + }, + { + "fieldname": "state", + "fieldtype": "Data", + "label": "State" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break" + }, + { + "fieldname": "gcalendar_id", + "fieldtype": "Data", + "label": "Google Calendar ID", + "read_only": 1 + }, + { + "fieldname": "next_sync_token", + "fieldtype": "Data", + "hidden": 1, + "label": "Next Sync Token" + } + ], + "modified": "2019-07-06 17:54:09.450100", + "modified_by": "Administrator", + "module": "Integrations", + "name": "Google Calendar", + "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, + "if_owner": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py new file mode 100644 index 0000000000..9868e400e0 --- /dev/null +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +SCOPES = 'https://www.googleapis.com/auth/calendar' +AUTHORIZATION_BASE_URL = "https://accounts.google.com/o/oauth2/v2/auth" + +SCOPES = "https://www.googleapis.com/auth/contacts" +REQUEST = "https://people.googleapis.com/v1/people/me/connections" +PARAMS = {"personFields": "names,emailAddresses,organizations,phoneNumbers"} + +class GoogleCalendar(Document): + + def validate(self): + if not frappe.db.get_single_value("Google Settings", "enable"): + frappe.throw(_("Enable Google API in Google Settings.")) + + def get_access_token(self): + google_settings = frappe.get_doc("Google Settings") + + if not google_settings.enable: + frappe.throw(_("Google Calendar Integration is disabled.")) + + if not self.refresh_token: + button_label = frappe.bold(_('Allow Google Calendar Access')) + raise frappe.ValidationError(_("Click on {0} to generate Refresh Token.").format(button_label)) + + data = { + "client_id": google_settings.client_id, + "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), + "refresh_token": self.get_password(fieldname="refresh_token", raise_exception=False), + "grant_type": "refresh_token", + "scope": SCOPES + } + + try: + r = requests.post("https://www.googleapis.com/oauth2/v4/token", data=data).json() + except requests.exceptions.HTTPError: + button_label = frappe.bold(_('Allow Google Calendar Access')) + frappe.throw(_("Something went wrong during the token generation. Click on {0} to generate a new one.").format(button_label)) + + return r.get("access_token") + +@frappe.whitelist() +def google_callback(code=None, state=None, account=None): + redirect_uri = get_request_site_address(True) + "?cmd=frappe.integrations.doctype.gcalendar_settings.gcalendar_settings.google_callback" + if account is not None: + frappe.cache().hset("gcalendar_account","GCalendar Account", account) + doc = frappe.get_doc("GCalendar Settings") + if code is None: + return { + 'url': 'https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&response_type=code&prompt=consent&client_id={}&include_granted_scopes=true&scope={}&redirect_uri={}'.format(doc.client_id, SCOPES, redirect_uri) + } + else: + try: + account = frappe.get_doc("Google Calendar", frappe.cache().hget("gcalendar_account", "GCalendar Account")) + data = {'code': code, + 'client_id': doc.client_id, + 'client_secret': doc.get_password(fieldname='client_secret',raise_exception=False), + 'redirect_uri': redirect_uri, + 'grant_type': 'authorization_code'} + + r = requests.post('https://www.googleapis.com/oauth2/v4/token', data=data).json() + frappe.db.set_value("GCalendar Account", account.name, "authorization_code", code) + if 'access_token' in r: + frappe.db.set_value("GCalendar Account", account.name, "session_token", r['access_token']) + if 'refresh_token' in r: + frappe.db.set_value("GCalendar Account", account.name, "refresh_token", r['refresh_token']) + frappe.db.commit() + frappe.local.response["type"] = "redirect" + frappe.local.response["location"] = "/integrations/gcalendar-success.html" + return + except Exception as e: + frappe.throw(e.message) + +@frappe.whitelist() +def authorize_access(g_calendar, reauthorize=None): + """ + If no Authorization code get it from Google and then request for Refresh Token. + Google Contact Name is set to flags to set_value after Authorization Code is obtained. + """ + + google_settings = frappe.get_doc("Google Settings") + google_calendar = frappe.get_doc("Google Calendar", g_calendar) + + redirect_uri = get_request_site_address(True) + "?cmd=frappe.integrations.doctype.google_calendar.google_calendar.google_callback" + + if not google_calendar.authorization_code or reauthorize: + frappe.cache().hset("google_calendar", "google_calendar", google_calendar.name) + return google_callback(client_id=google_settings.client_id, redirect_uri=redirect_uri) + else: + try: + data = { + "code": google_calendar.authorization_code, + "client_id": google_settings.client_id, + "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), + "redirect_uri": redirect_uri, + "grant_type": "authorization_code" + } + r = requests.post("https://www.googleapis.com/oauth2/v4/token", data=data).json() + + if "refresh_token" in r: + frappe.db.set_value("Google Calendar", google_calendar.name, "refresh_token", r.get("refresh_token")) + frappe.db.commit() + + frappe.local.response["type"] = "redirect" + frappe.local.response["location"] = "/desk#Form/Google%20Contacts/{}".format(google_calendar.name) + + frappe.msgprint(_("Google Calendar has been configured.")) + except Exception as e: + frappe.throw(e) + +@frappe.whitelist() +def google_callback(client_id=None, redirect_uri=None, code=None): + """ + Authorization code is sent to callback as per the API configuration + """ + if code is None: + return { + "url": "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&response_type=code&prompt=consent&client_id={}&include_granted_scopes=true&scope={}&redirect_uri={}".format(client_id, SCOPES, redirect_uri) + } + else: + google_calendar = frappe.cache().hget("google_calendar", "google_calendar") + frappe.db.set_value("Google Calendar", google_calendar, "authorization_code", code) + frappe.db.commit() + + authorize_access(google_calendar) \ No newline at end of file diff --git a/frappe/integrations/doctype/google_calendar/test_google_calendar.py b/frappe/integrations/doctype/google_calendar/test_google_calendar.py new file mode 100644 index 0000000000..0fad81d7f5 --- /dev/null +++ b/frappe/integrations/doctype/google_calendar/test_google_calendar.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestGoogleCalendar(unittest.TestCase): + pass From 818ea57687a9d4221ba6250afa7d105d984fbd7b Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 20 Jul 2019 22:13:44 +0530 Subject: [PATCH 002/203] fix: ux changes --- frappe/config/integrations.py | 5 +++ .../google_calendar/google_calendar.json | 37 ++++++++----------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/frappe/config/integrations.py b/frappe/config/integrations.py index 224fa65d13..2bfbae48ad 100644 --- a/frappe/config/integrations.py +++ b/frappe/config/integrations.py @@ -116,6 +116,11 @@ def get_data(): "type": "doctype", "name": "Google Contacts", "description": _("Google Contacts Integration."), + }, + { + "type": "doctype", + "name": "Google Calendar", + "description": _("Google Calendar Integration."), } ] } diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.json b/frappe/integrations/doctype/google_calendar/google_calendar.json index 8cd7608f67..bf67cc20be 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.json +++ b/frappe/integrations/doctype/google_calendar/google_calendar.json @@ -8,17 +8,16 @@ "enable", "sb_00", "user", + "allow_calendar_access", + "cb_00", "calendar_name", "section_break_3", - "allow_google_access", "section_break_4", "refresh_token", "authorization_code", "column_break_6", "session_token", - "state", - "section_break_9", - "gcalendar_id", + "google_calendar_id", "next_sync_token" ], "fields": [ @@ -55,16 +54,9 @@ "fieldname": "section_break_3", "fieldtype": "Section Break" }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "allow_google_access", - "fieldtype": "Button", - "label": "Allow GCalendar Access" - }, { "fieldname": "section_break_4", - "fieldtype": "Section Break", - "hidden": 1 + "fieldtype": "Section Break" }, { "fieldname": "refresh_token", @@ -86,28 +78,29 @@ "label": "Session Token" }, { - "fieldname": "state", + "fieldname": "next_sync_token", "fieldtype": "Data", - "label": "State" + "hidden": 1, + "label": "Next Sync Token" }, { - "fieldname": "section_break_9", - "fieldtype": "Section Break" + "depends_on": "eval:!doc.__islocal", + "fieldname": "allow_calendar_access", + "fieldtype": "Button", + "label": "Allow Calendar Access" }, { - "fieldname": "gcalendar_id", + "fieldname": "google_calendar_id", "fieldtype": "Data", "label": "Google Calendar ID", "read_only": 1 }, { - "fieldname": "next_sync_token", - "fieldtype": "Data", - "hidden": 1, - "label": "Next Sync Token" + "fieldname": "cb_00", + "fieldtype": "Column Break" } ], - "modified": "2019-07-06 17:54:09.450100", + "modified": "2019-07-20 22:12:22.389101", "modified_by": "Administrator", "module": "Integrations", "name": "Google Calendar", From 70f016b1f1c79f543747282610df025c0007014d Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 20 Jul 2019 22:21:32 +0530 Subject: [PATCH 003/203] fix: remove unwanted code --- .../google_calendar/google_calendar.py | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 9868e400e0..82a3e71c92 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -45,38 +45,6 @@ class GoogleCalendar(Document): return r.get("access_token") -@frappe.whitelist() -def google_callback(code=None, state=None, account=None): - redirect_uri = get_request_site_address(True) + "?cmd=frappe.integrations.doctype.gcalendar_settings.gcalendar_settings.google_callback" - if account is not None: - frappe.cache().hset("gcalendar_account","GCalendar Account", account) - doc = frappe.get_doc("GCalendar Settings") - if code is None: - return { - 'url': 'https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&response_type=code&prompt=consent&client_id={}&include_granted_scopes=true&scope={}&redirect_uri={}'.format(doc.client_id, SCOPES, redirect_uri) - } - else: - try: - account = frappe.get_doc("Google Calendar", frappe.cache().hget("gcalendar_account", "GCalendar Account")) - data = {'code': code, - 'client_id': doc.client_id, - 'client_secret': doc.get_password(fieldname='client_secret',raise_exception=False), - 'redirect_uri': redirect_uri, - 'grant_type': 'authorization_code'} - - r = requests.post('https://www.googleapis.com/oauth2/v4/token', data=data).json() - frappe.db.set_value("GCalendar Account", account.name, "authorization_code", code) - if 'access_token' in r: - frappe.db.set_value("GCalendar Account", account.name, "session_token", r['access_token']) - if 'refresh_token' in r: - frappe.db.set_value("GCalendar Account", account.name, "refresh_token", r['refresh_token']) - frappe.db.commit() - frappe.local.response["type"] = "redirect" - frappe.local.response["location"] = "/integrations/gcalendar-success.html" - return - except Exception as e: - frappe.throw(e.message) - @frappe.whitelist() def authorize_access(g_calendar, reauthorize=None): """ From 183adf165905a38ecc42bb19c5ee764a110d8f23 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 20 Jul 2019 22:29:06 +0530 Subject: [PATCH 004/203] fix: frappe call for authorization --- .../google_calendar/google_calendar.js | 21 +++++++++++++++++-- .../google_calendar/google_calendar.json | 16 +++++++------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.js b/frappe/integrations/doctype/google_calendar/google_calendar.js index a5af22a244..7eb138ddc2 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.js +++ b/frappe/integrations/doctype/google_calendar/google_calendar.js @@ -2,7 +2,24 @@ // For license information, please see license.txt frappe.ui.form.on('Google Calendar', { - // refresh: function(frm) { + authorize_google_calendar_access: function(frm) { + let reauthorize = 0; + if(frm.doc.authorization_code) { + reauthorize = 1; + } - // } + frappe.call({ + method: "frappe.integrations.doctype.google_calendar.google_calendar.authorize_access", + args: { + "g_contact": frm.doc.name, + "reauthorize": reauthorize + }, + callback: function(r) { + if(!r.exc) { + frm.save(); + window.open(r.message.url); + } + } + }); + } }); diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.json b/frappe/integrations/doctype/google_calendar/google_calendar.json index bf67cc20be..9234e26f9f 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.json +++ b/frappe/integrations/doctype/google_calendar/google_calendar.json @@ -8,7 +8,7 @@ "enable", "sb_00", "user", - "allow_calendar_access", + "authorize_google_calendar_access", "cb_00", "calendar_name", "section_break_3", @@ -83,12 +83,6 @@ "hidden": 1, "label": "Next Sync Token" }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "allow_calendar_access", - "fieldtype": "Button", - "label": "Allow Calendar Access" - }, { "fieldname": "google_calendar_id", "fieldtype": "Data", @@ -98,9 +92,15 @@ { "fieldname": "cb_00", "fieldtype": "Column Break" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "authorize_google_calendar_access", + "fieldtype": "Button", + "label": "Authorize Google Calendar Access" } ], - "modified": "2019-07-20 22:12:22.389101", + "modified": "2019-07-20 22:28:32.989673", "modified_by": "Administrator", "module": "Integrations", "name": "Google Calendar", From d723c7947eb25dc8c40bbe2b1e632c3b52c72bd1 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 20 Jul 2019 22:31:08 +0530 Subject: [PATCH 005/203] fix: frappe call parameter --- frappe/integrations/doctype/google_calendar/google_calendar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.js b/frappe/integrations/doctype/google_calendar/google_calendar.js index 7eb138ddc2..905568df39 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.js +++ b/frappe/integrations/doctype/google_calendar/google_calendar.js @@ -11,7 +11,7 @@ frappe.ui.form.on('Google Calendar', { frappe.call({ method: "frappe.integrations.doctype.google_calendar.google_calendar.authorize_access", args: { - "g_contact": frm.doc.name, + "g_calendar": frm.doc.name, "reauthorize": reauthorize }, callback: function(r) { From e2f2d1f9f80cc9a7684af827bc70f4ff766f1ec7 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 20 Jul 2019 22:49:55 +0530 Subject: [PATCH 006/203] fix: import _ --- .../doctype/google_calendar/google_calendar.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 82a3e71c92..29f5fd9ed8 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -4,14 +4,12 @@ from __future__ import unicode_literals import frappe +import requests +from frappe import _ from frappe.model.document import Document +from frappe.utils import get_request_site_address SCOPES = 'https://www.googleapis.com/auth/calendar' -AUTHORIZATION_BASE_URL = "https://accounts.google.com/o/oauth2/v2/auth" - -SCOPES = "https://www.googleapis.com/auth/contacts" -REQUEST = "https://people.googleapis.com/v1/people/me/connections" -PARAMS = {"personFields": "names,emailAddresses,organizations,phoneNumbers"} class GoogleCalendar(Document): From 5850ed71f407a84df1869c4017a5cafa57c0e5d7 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 22 Jul 2019 11:16:00 +0530 Subject: [PATCH 007/203] fix: remove unwanted files --- .../event_to_gcalendar/__init__.py | 0 .../event_to_gcalendar.json | 100 ------- .../gcalendar_to_event/__init__.py | 140 ---------- .../gcalendar_to_event.json | 105 -------- .../gcalendar_sync/gcalendar_sync.json | 22 -- .../google_calendar/google_calendar.py | 243 +++++++++++++++++- 6 files changed, 240 insertions(+), 370 deletions(-) delete mode 100644 frappe/integrations/data_migration_mapping/event_to_gcalendar/__init__.py delete mode 100644 frappe/integrations/data_migration_mapping/event_to_gcalendar/event_to_gcalendar.json delete mode 100644 frappe/integrations/data_migration_mapping/gcalendar_to_event/__init__.py delete mode 100644 frappe/integrations/data_migration_mapping/gcalendar_to_event/gcalendar_to_event.json delete mode 100644 frappe/integrations/data_migration_plan/gcalendar_sync/gcalendar_sync.json diff --git a/frappe/integrations/data_migration_mapping/event_to_gcalendar/__init__.py b/frappe/integrations/data_migration_mapping/event_to_gcalendar/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/integrations/data_migration_mapping/event_to_gcalendar/event_to_gcalendar.json b/frappe/integrations/data_migration_mapping/event_to_gcalendar/event_to_gcalendar.json deleted file mode 100644 index 6d2a6020a5..0000000000 --- a/frappe/integrations/data_migration_mapping/event_to_gcalendar/event_to_gcalendar.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "creation": "2017-12-19 14:42:54.264536", - "docstatus": 0, - "doctype": "Data Migration Mapping", - "fields": [ - { - "is_child_table": 0, - "local_fieldname": "subject", - "remote_fieldname": "summary" - }, - { - "is_child_table": 0, - "local_fieldname": "description", - "remote_fieldname": "description" - }, - { - "is_child_table": 0, - "local_fieldname": "starts_on", - "remote_fieldname": "start_datetime" - }, - { - "is_child_table": 0, - "local_fieldname": "ends_on", - "remote_fieldname": "end_datetime" - }, - { - "is_child_table": 0, - "local_fieldname": "all_day", - "remote_fieldname": "all_day" - }, - { - "is_child_table": 0, - "local_fieldname": "repeat_this_event", - "remote_fieldname": "repeat_this_event" - }, - { - "is_child_table": 0, - "local_fieldname": "repeat_on", - "remote_fieldname": "repeat_on" - }, - { - "is_child_table": 0, - "local_fieldname": "repeat_till", - "remote_fieldname": "repeat_till" - }, - { - "is_child_table": 0, - "local_fieldname": "monday", - "remote_fieldname": "monday" - }, - { - "is_child_table": 0, - "local_fieldname": "tuesday", - "remote_fieldname": "tuesday" - }, - { - "is_child_table": 0, - "local_fieldname": "wednesday", - "remote_fieldname": "wednesday" - }, - { - "is_child_table": 0, - "local_fieldname": "thursday", - "remote_fieldname": "thursday" - }, - { - "is_child_table": 0, - "local_fieldname": "friday", - "remote_fieldname": "friday" - }, - { - "is_child_table": 0, - "local_fieldname": "saturday", - "remote_fieldname": "saturday" - }, - { - "is_child_table": 0, - "local_fieldname": "sunday", - "remote_fieldname": "sunday" - }, - { - "is_child_table": 0, - "local_fieldname": "name", - "remote_fieldname": "name" - } - ], - "idx": 0, - "local_doctype": "Event", - "local_primary_key": "gcalendar_sync_id", - "mapping_name": "Event to GCalendar", - "mapping_type": "Push", - "migration_id_field": "gcalendar_sync_id", - "modified": "2019-03-26 10:16:45.400150", - "modified_by": "Administrator", - "name": "Event to GCalendar", - "owner": "Administrator", - "page_length": 10, - "remote_objectname": "Events", - "remote_primary_key": "id" -} \ No newline at end of file diff --git a/frappe/integrations/data_migration_mapping/gcalendar_to_event/__init__.py b/frappe/integrations/data_migration_mapping/gcalendar_to_event/__init__.py deleted file mode 100644 index 5518871c97..0000000000 --- a/frappe/integrations/data_migration_mapping/gcalendar_to_event/__init__.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -import frappe -from datetime import datetime, timedelta -from dateutil.parser import parse -from pytz import timezone -from frappe.utils import add_days - - -def pre_process(events): - if events["status"] == "cancelled": - if frappe.db.exists("Event", dict(gcalendar_sync_id=events["id"])): - e = frappe.get_doc("Event", dict(gcalendar_sync_id=events["id"])) - frappe.delete_doc("Event", e.name) - return {} - - elif events["status"] == "confirmed": - if 'date' in events["start"]: - datevar = 'date' - start_dt = parse(events["start"]['date']) - end_dt = add_days(parse(events["end"]['date']), -1) - elif 'dateTime' in events["start"]: - datevar = 'dateTime' - start_dt = parse(events["start"]['dateTime']) - end_dt = parse(events["end"]['dateTime']) - - if start_dt.tzinfo is None or start_dt.tzinfo.utcoffset(start_dt) is None: - if "timeZone" in events["start"]: - event_tz = events["start"]["timeZone"] - else: - event_tz = events["calendar_tz"] - start_dt = timezone(event_tz).localize(start_dt) - - if end_dt.tzinfo is None or end_dt.tzinfo.utcoffset(end_dt) is None: - if "timeZone" in events["end"]: - event_tz = events["end"]["timeZone"] - else: - event_tz = events["calendar_tz"] - end_dt = timezone(event_tz).localize(end_dt) - - default_tz = frappe.db.get_value("System Settings", None, "time_zone") - - event = { - 'id': events["id"], - 'summary': events["summary"], - 'start_datetime': start_dt.astimezone(timezone(default_tz)).strftime('%Y-%m-%d %H:%M:%S'), - 'end_datetime': end_dt.astimezone(timezone(default_tz)).strftime('%Y-%m-%d %H:%M:%S'), - 'account': events['account'] - } - - if "recurrence" in events: - recurrence = get_recurrence_event_fields_value(events['recurrence'][0], events["start"][datevar]) - - event.update(recurrence) - - if 'description' in events: - event.update({'description': events["description"]}) - else: - event.update({'description': ""}) - - if datevar == 'date': - event.update({'all_day': 1}) - - return event - - -def get_recurrence_event_fields_value(recur_rule, starts_on): - repeat_on = "" - repeat_till = "" - repeat_days = {} - # get recurrence rule from string - for _str in recur_rule.split(";"): - if "RRULE:FREQ" in _str: - repeat_every = _str.split("=")[1] - if repeat_every == "DAILY": repeat_on = "Every Day" - elif repeat_every == "WEEKLY": repeat_on = "Every Week" - elif repeat_every == "MONTHLY": repeat_on = "Every Month" - else: repeat_on = "Every Year" - elif "UNTIL" in _str: - # get repeat till - date = parse(_str.split("=")[1]) - repeat_till = get_repeat_till_date(date) - elif "COUNT" in _str: - # get repeat till - date = parse(starts_on) - repeat_till = get_repeat_till_date(date, count=_str.split("=")[1], repeat_on=repeat_on) - elif "BYDAY" in _str: - days = _str.split("=")[1] - repeat_days.update({ - "sunday": 1 if "SU" in days else 0, - "monday": 1 if "MO" in days else 0, - "tuesday": 1 if "TU" in days else 0, - "wednesday": 1 if "WD" in days else 0, - "thursday": 1 if "TH" in days else 0, - "friday": 1 if "FR" in days else 0, - "saturday": 1 if "SA" in days else 0, - }) - repeat_on = "Every Day" - - recurrence = { - "repeat_on": repeat_on, - "repeat_till": repeat_till, - "repeat_this_event": 1 - } - - if repeat_days: - recurrence.update(repeat_days) - - return recurrence - - -def get_repeat_till_date(date, count=None, repeat_on=None): - if count: - if repeat_on == "Every Day": - # add days - date = date + timedelta(days=int(count)) - elif repeat_on == "Every Week": - # add weeks - date = date + timedelta(weeks=int(count)) - elif repeat_on == "Every Month": - # add months - date = add_months(date, int(count)) - elif repeat_on == "Every Year": - # add years - date = add_months(date, int(count) * 12) - else: - # set default value - date = add_months(date, int(count)) - - return date.strftime("%Y-%m-%d") - -def add_months(date, count): - import calendar - - month = date.month - 1 + count - year = date.year + month / 12 - month = month % 12 + 1 - day = min(date.day,calendar.monthrange(year,month)[1]) - return datetime(year,month,day) diff --git a/frappe/integrations/data_migration_mapping/gcalendar_to_event/gcalendar_to_event.json b/frappe/integrations/data_migration_mapping/gcalendar_to_event/gcalendar_to_event.json deleted file mode 100644 index a4ca740ce5..0000000000 --- a/frappe/integrations/data_migration_mapping/gcalendar_to_event/gcalendar_to_event.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "creation": "2018-02-16 13:07:13.325914", - "docstatus": 0, - "doctype": "Data Migration Mapping", - "fields": [ - { - "is_child_table": 0, - "local_fieldname": "subject", - "remote_fieldname": "summary" - }, - { - "is_child_table": 0, - "local_fieldname": "description", - "remote_fieldname": "description" - }, - { - "is_child_table": 0, - "local_fieldname": "starts_on", - "remote_fieldname": "start_datetime" - }, - { - "is_child_table": 0, - "local_fieldname": "ends_on", - "remote_fieldname": "end_datetime" - }, - { - "is_child_table": 0, - "local_fieldname": "all_day", - "remote_fieldname": "all_day" - }, - { - "is_child_table": 0, - "local_fieldname": "repeat_this_event", - "remote_fieldname": "repeat_this_event" - }, - { - "is_child_table": 0, - "local_fieldname": "repeat_on", - "remote_fieldname": "repeat_on" - }, - { - "is_child_table": 0, - "local_fieldname": "repeat_till", - "remote_fieldname": "repeat_till" - }, - { - "is_child_table": 0, - "local_fieldname": "monday", - "remote_fieldname": "monday" - }, - { - "is_child_table": 0, - "local_fieldname": "tuesday", - "remote_fieldname": "tuesday" - }, - { - "is_child_table": 0, - "local_fieldname": "wednesday", - "remote_fieldname": "wednesday" - }, - { - "is_child_table": 0, - "local_fieldname": "thursday", - "remote_fieldname": "thursday" - }, - { - "is_child_table": 0, - "local_fieldname": "friday", - "remote_fieldname": "friday" - }, - { - "is_child_table": 0, - "local_fieldname": "saturday", - "remote_fieldname": "saturday" - }, - { - "is_child_table": 0, - "local_fieldname": "sunday", - "remote_fieldname": "sunday" - }, - { - "is_child_table": 0, - "local_fieldname": "gcalendar_sync_id", - "remote_fieldname": "id" - }, - { - "is_child_table": 0, - "local_fieldname": "owner", - "remote_fieldname": "account" - } - ], - "idx": 0, - "local_doctype": "Event", - "local_primary_key": "gcalendar_sync_id", - "mapping_name": "GCalendar to Event", - "mapping_type": "Pull", - "migration_id_field": "gcalendar_sync_id", - "modified": "2019-03-26 10:16:45.426138", - "modified_by": "Administrator", - "name": "GCalendar to Event", - "owner": "Administrator", - "page_length": 250, - "remote_objectname": "Events", - "remote_primary_key": "id" -} \ No newline at end of file diff --git a/frappe/integrations/data_migration_plan/gcalendar_sync/gcalendar_sync.json b/frappe/integrations/data_migration_plan/gcalendar_sync/gcalendar_sync.json deleted file mode 100644 index 9eef669203..0000000000 --- a/frappe/integrations/data_migration_plan/gcalendar_sync/gcalendar_sync.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "creation": "2018-03-23 19:10:23.715161", - "docstatus": 0, - "doctype": "Data Migration Plan", - "idx": 22, - "mappings": [ - { - "enabled": 1, - "mapping": "Event to GCalendar" - }, - { - "enabled": 1, - "mapping": "GCalendar to Event" - } - ], - "modified": "2019-03-26 10:16:45.369037", - "modified_by": "Administrator", - "module": "Integrations", - "name": "GCalendar Sync", - "owner": "Administrator", - "plan_name": "GCalendar Sync" -} \ No newline at end of file diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 29f5fd9ed8..b4ff9b9ecf 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -9,7 +9,7 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import get_request_site_address -SCOPES = 'https://www.googleapis.com/auth/calendar' +SCOPES = 'https://www.googleapis.com/auth/calendar/v3' class GoogleCalendar(Document): @@ -74,7 +74,7 @@ def authorize_access(g_calendar, reauthorize=None): frappe.db.commit() frappe.local.response["type"] = "redirect" - frappe.local.response["location"] = "/desk#Form/Google%20Contacts/{}".format(google_calendar.name) + frappe.local.response["location"] = "/desk#Form/Google%20Calendar/{}".format(google_calendar.name) frappe.msgprint(_("Google Calendar has been configured.")) except Exception as e: @@ -94,4 +94,241 @@ def google_callback(client_id=None, redirect_uri=None, code=None): frappe.db.set_value("Google Calendar", google_calendar, "authorization_code", code) frappe.db.commit() - authorize_access(google_calendar) \ No newline at end of file + authorize_access(google_calendar) + +class GoogleCalendarSync(): + def __init__(self, g_calendar): + google_settings = frappe.get_doc("Google Settings") + + g_calendar = frappe.get_doc("Google Calendar", g_calendar) + + credentials_dict = { + 'refresh_token': g_calendar.refresh_token, + 'token_uri': 'https://www.googleapis.com/oauth2/v4/token', + 'client_id': google_settings.client_id, + 'client_secret': google_settings.get_password(fieldname='client_secret', raise_exception=False), + 'scopes':'https://www.googleapis.com/auth/calendar' + } + + credentials = google.oauth2.credentials.Credentials(**credentials_dict) + google_calendar = googleapiclient.discovery.build('calendar', 'v3', credentials=credentials) + + self.check_remote_calendar() + + def check_remote_calendar(self): + def _create_calendar(): + timezone = frappe.db.get_value("System Settings", None, "time_zone") + calendar = { + 'summary': self.account.calendar_name, + 'timeZone': timezone + } + try: + created_calendar = self.gcalendar.calendars().insert(body=calendar).execute() + frappe.db.set_value("GCalendar Account", self.account.name, "gcalendar_id", created_calendar["id"]) + except Exception: + frappe.log_error(frappe.get_traceback()) + + try: + if self.account.gcalendar_id is not None: + try: + self.gcalendar.calendars().get(calendarId=self.account.gcalendar_id).execute() + except Exception: + frappe.log_error(frappe.get_traceback()) + else: + _create_calendar() + except HttpError as err: + if err.resp.status in [403, 500, 503]: + time.sleep(5) + elif err.resp.status in [404]: + _create_calendar() + else: raise + + + def get(self, remote_objectname, fields=None, filters=None, start=0, page_length=10): + return self.get_events(remote_objectname, filters, page_length) + + def insert(self, doctype, doc): + if doctype == 'Events': + d = frappe.get_doc("Event", doc["name"]) + if has_permission(d, self.account.name): + try: + doctype = "Event" + e = self.insert_events(doctype, doc) + return e + except Exception: + frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error") + + + def update(self, doctype, doc, migration_id): + if doctype == 'Events': + d = frappe.get_doc("Event", doc["name"]) + if has_permission(d, self.account.name): + if migration_id is not None: + try: + doctype = "Event" + return self.update_events(doctype, doc, migration_id) + except Exception: + frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error") + + def delete(self, doctype, migration_id): + if doctype == 'Events': + try: + return self.delete_events(migration_id) + except Exception: + frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error") + + def get_events(self, remote_objectname, filters, page_length): + page_token = None + results = [] + events = {"items": []} + while True: + try: + events = self.gcalendar.events().list(calendarId=self.account.gcalendar_id, maxResults=page_length, + singleEvents=False, showDeleted=True, syncToken=self.account.next_sync_token or None).execute() + except HttpError as err: + if err.resp.status in [410]: + events = self.gcalendar.events().list(calendarId=self.account.gcalendar_id, maxResults=page_length, + singleEvents=False, showDeleted=True, timeMin=add_years(None, -1).strftime('%Y-%m-%dT%H:%M:%SZ')).execute() + else: + frappe.log_error(err.resp, "GCalendar Events Fetch Error") + for event in events['items']: + event.update({'account': self.account.name}) + event.update({'calendar_tz': events['timeZone']}) + results.append(event) + + page_token = events.get('nextPageToken') + if not page_token: + if events.get('nextSyncToken'): + frappe.db.set_value("GCalendar Account", self.connector.username, "next_sync_token", events.get('nextSyncToken')) + break + return list(results) + + def insert_events(self, doctype, doc, migration_id=None): + event = { + 'summary': doc.summary, + 'description': doc.description + } + + dates = self.return_dates(doc) + event.update(dates) + + if migration_id: + event.update({"id": migration_id}) + + if doc.repeat_this_event != 0: + recurrence = self.return_recurrence(doctype, doc) + if not not recurrence: + event.update({"recurrence": ["RRULE:" + str(recurrence)]}) + + try: + remote_event = self.gcalendar.events().insert(calendarId=self.account.gcalendar_id, body=event).execute() + return {self.name_field: remote_event["id"]} + except Exception: + frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error") + + def update_events(self, doctype, doc, migration_id): + try: + event = self.gcalendar.events().get(calendarId=self.account.gcalendar_id, eventId=migration_id).execute() + event = { + 'summary': doc.summary, + 'description': doc.description + } + + if doc.event_type == "Cancel": + event.update({"status": "cancelled"}) + + dates = self.return_dates(doc) + event.update(dates) + + if doc.repeat_this_event != 0: + recurrence = self.return_recurrence(doctype, doc) + if recurrence: + event.update({"recurrence": ["RRULE:" + str(recurrence)]}) + + try: + updated_event = self.gcalendar.events().update(calendarId=self.account.gcalendar_id, eventId=migration_id, body=event).execute() + return {self.name_field: updated_event["id"]} + except Exception as e: + frappe.log_error(e, "GCalendar Synchronization Error") + except HttpError as err: + if err.resp.status in [404]: + self.insert_events(doctype, doc, migration_id) + else: + frappe.log_error(err.resp, "GCalendar Synchronization Error") + + def delete_events(self, migration_id): + try: + self.gcalendar.events().delete(calendarId=self.account.gcalendar_id, eventId=migration_id).execute() + except HttpError as err: + if err.resp.status in [410]: + pass + + def return_dates(self, doc): + timezone = frappe.db.get_value("System Settings", None, "time_zone") + if doc.end_datetime is None: + doc.end_datetime = doc.start_datetime + if doc.all_day == 1: + return { + 'start': { + 'date': doc.start_datetime.date().isoformat(), + 'timeZone': timezone, + }, + 'end': { + 'date': add_days(doc.end_datetime.date(), 1).isoformat(), + 'timeZone': timezone, + } + } + else: + return { + 'start': { + 'dateTime': doc.start_datetime.isoformat(), + 'timeZone': timezone, + }, + 'end': { + 'dateTime': doc.end_datetime.isoformat(), + 'timeZone': timezone, + } + } + + def return_recurrence(self, doctype, doc): + e = frappe.get_doc(doctype, doc.name) + if e.repeat_till is not None: + end_date = datetime.combine(e.repeat_till, datetime.min.time()).strftime('UNTIL=%Y%m%dT%H%M%SZ') + else: + end_date = None + + day = [] + if e.repeat_on == "Every Day": + if e.monday == 1: + day.append("MO") + if e.tuesday == 1: + day.append("TU") + if e.wednesday == 1: + day.append("WE") + if e.thursday == 1: + day.append("TH") + if e.friday == 1: + day.append("FR") + if e.saturday == 1: + day.append("SA") + if e.sunday == 1: + day.append("SU") + + day = "BYDAY=" + ",".join(str(d) for d in day) + frequency = "FREQ=WEEKLY" + + elif e.repeat_on == "Every Week": + frequency = "FREQ=WEEKLY" + elif e.repeat_on == "Every Month": + frequency = "FREQ=MONTHLY;BYDAY=SU,MO,TU,WE,TH,FR,SA;BYSETPOS=-1" + end_date = datetime.combine(add_days(e.repeat_till, 1), datetime.min.time()).strftime('UNTIL=%Y%m%dT%H%M%SZ') + elif e.repeat_on == "Every Year": + frequency = "FREQ=YEARLY" + else: + return None + + wst = "WKST=SU" + + elements = [frequency, end_date, wst, day] + + return ";".join(str(e) for e in elements if e is not None and not not e) From 5fdc33d9bcec673d42f1628fce1827f55f9261f6 Mon Sep 17 00:00:00 2001 From: sahil28297 <37302950+sahil28297@users.noreply.github.com> Date: Mon, 22 Jul 2019 17:30:15 +0530 Subject: [PATCH 008/203] Release Version 12 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index c19295a95c..62576bea8e 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -23,7 +23,7 @@ if sys.version[0] == '2': reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '11.1.36' +__version__ = '12.0.0' __title__ = "Frappe Framework" local = Local() From 93022be8b58512670d38acb69a06399a26f37f3b Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 22 Jul 2019 17:38:04 +0530 Subject: [PATCH 009/203] changelog: release 12.0.0 --- frappe/change_log/v12/v12_0_0.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 frappe/change_log/v12/v12_0_0.md diff --git a/frappe/change_log/v12/v12_0_0.md b/frappe/change_log/v12/v12_0_0.md new file mode 100644 index 0000000000..897b351122 --- /dev/null +++ b/frappe/change_log/v12/v12_0_0.md @@ -0,0 +1,29 @@ +# Version 12 Release Notes + +### UI/UX Enhancements +1. [New Desktop](/docs/user/manual/en/using-erpnext/desktop) +1. [Keyboard Navigation](/docs/user/manual/en/using-erpnext/articles/keyboard-shortcuts) +1. [Link Preview](/version-12/release-notes/features#link-preview) +1. [New Upload Dialog](/version-12/release-notes/features#new-upload-dialog) +1. [Frequently visited links appear in Awesomebar results](/version-12/release-notes/features#frequently-visited-links-appear-in-awesomebar-results) +1. [Full Width Container]((/version-12/release-notes/features#full-width-container)) +1. [List View Enhancements](/version-12/release-notes/features#list-view-enhancements) + +### New Automation Module +1. [Assignment Rule](/docs/user/manual/en/setting-up/automation/assignment-rule) +1. [Milestones](/docs/user/manual/en/setting-up/automation/milestone-tracker) +1. [Auto Repeat](/docs/user/manual/en/setting-up/automation/auto-repeat) + +### Other Changes & Enhancements +1. [Document Follow](/docs/user/manual/en/setting-up/email/document-follow) +1. [Energy Points](/docs/user/manual/en/setting-up/energy-point-system) +1. [Dashboards](/docs/user/manual/en/customize-erpnext/dashboard) +1. [Disable customization for single doctypes](/version-12/release-notes/features#disable-customization-for-single-doctypes) +1. [Email Linking](/docs/user/manual/en/setting-up/email/linking-emails-to-document) +1. [Google Contacts](/docs/user/manual/en/erpnext_integration/google_contacts) +1. [PDF Encryption](/version-12/release-notes/features#pdf-encryption) +1. [Raw Printing](/docs/user/manual/en/setting-up/print/raw-printing) +1. [Web Form Refactor](/version-12/release-notes/features#web-form-refactor) +1. [Website Refactor](/docs/user/manual/en/website) +1. [Added Track Views field to Customize Form](/version-12/release-notes/features#added-track-views-field-to-customize-form) +1. [Add custom columns to any report](/version-12/release-notes/features#add-custom-columns-to-any-report) From d06abbd85284e850105f7a1ce76ba76fabee5b8b Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 23 Jul 2019 16:46:15 +0530 Subject: [PATCH 010/203] feat: Option to skip failing patches in migrate (#7959) --- frappe/commands/site.py | 5 +++-- frappe/migrate.py | 4 ++-- frappe/modules/patch_handler.py | 20 ++++++++++++++------ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 0eb6a36098..5668759882 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -220,8 +220,9 @@ def disable_user(context, email): @click.command('migrate') @click.option('--rebuild-website', help="Rebuild webpages after migration") +@click.option('--skip-failing', is_flag=True, help="Skip patches that fail to run") @pass_context -def migrate(context, rebuild_website=False): +def migrate(context, rebuild_website=False, skip_failing=False): "Run patches, sync schema and rebuild files/translations" from frappe.migrate import migrate @@ -230,7 +231,7 @@ def migrate(context, rebuild_website=False): frappe.init(site=site) frappe.connect() try: - migrate(context.verbose, rebuild_website=rebuild_website) + migrate(context.verbose, rebuild_website=rebuild_website, skip_failing=skip_failing) finally: frappe.destroy() diff --git a/frappe/migrate.py b/frappe/migrate.py index 7b5ce55b36..6778a3f18f 100644 --- a/frappe/migrate.py +++ b/frappe/migrate.py @@ -17,7 +17,7 @@ from frappe.core.doctype.language.language import sync_languages from frappe.modules.utils import sync_customizations from frappe.utils import global_search -def migrate(verbose=True, rebuild_website=False): +def migrate(verbose=True, rebuild_website=False, skip_failing=False): '''Migrate all apps to the latest version, will: - run before migrate hooks - run patches @@ -45,7 +45,7 @@ def migrate(verbose=True, rebuild_website=False): frappe.get_attr(fn)() # run patches - frappe.modules.patch_handler.run_all() + frappe.modules.patch_handler.run_all(skip_failing) # sync frappe.model.sync.sync_all(verbose=verbose) frappe.translate.clear_cache() diff --git a/frappe/modules/patch_handler.py b/frappe/modules/patch_handler.py index 685d755fbd..b3237b8b76 100644 --- a/frappe/modules/patch_handler.py +++ b/frappe/modules/patch_handler.py @@ -19,23 +19,31 @@ import os class PatchError(Exception): pass -def run_all(): +def run_all(skip_failing=False): """run all pending patches""" executed = [p[0] for p in frappe.db.sql("""select patch from `tabPatch Log`""")] frappe.flags.final_patches = [] - for patch in get_all_patches(): - if patch and (patch not in executed): + + def run_patch(patch): + try: if not run_single(patchmodule = patch): log(patch + ': failed: STOPPED') raise PatchError(patch) + except Exception: + if not skip_failing: + raise + else: + log('Failed to execute patch') + + for patch in get_all_patches(): + if patch and (patch not in executed): + run_patch(patch) # patches to be run in the end for patch in frappe.flags.final_patches: patch = patch.replace('finally:', '') - if not run_single(patchmodule = patch): - log(patch + ': failed: STOPPED') - raise PatchError(patch) + run_patch(patch) def get_all_patches(): patches = [] From 17c2d2cb69420a3506ef4d64b631237d964142ba Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 23 Jul 2019 19:11:19 +0530 Subject: [PATCH 011/203] fix: Check is_private when checking for duplicates (#7957) * fix: Check is_private when checking for duplicates * fix: Fallback file_size --- frappe/core/doctype/file/file.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 47df90d18c..6e6cce52bf 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -188,14 +188,20 @@ class File(NestedSet): # check duplicate name # check duplicate assignement - n_records = frappe.db.sql("""select name from `tabFile` - where content_hash=%s - and name!=%s - and attached_to_doctype=%s - and attached_to_name=%s""", (self.content_hash, self.name, self.attached_to_doctype, - self.attached_to_name)) - if len(n_records) > 0: - self.duplicate_entry = n_records[0][0] + filters = { + 'content_hash': self.content_hash, + 'is_private': self.is_private, + 'name': ('!=', self.name) + } + if self.attached_to_doctype and self.attached_to_name: + filters.update({ + 'attached_to_doctype': self.attached_to_doctype, + 'attached_to_name': self.attached_to_name + }) + duplicate_file = frappe.db.get_value('File', filters) + + if duplicate_file: + self.duplicate_entry = duplicate_file frappe.throw(_("Same file has already been attached to the record"), frappe.DuplicateEntryError) @@ -451,7 +457,7 @@ class File(NestedSet): return self.file_url = unquote(self.file_url) - self.file_size = frappe.form_dict.file_size + self.file_size = frappe.form_dict.file_size or self.file_size def get_uploaded_content(self): @@ -484,7 +490,13 @@ class File(NestedSet): self.content_hash = get_content_hash(self.content) self.content_type = mimetypes.guess_type(self.file_name)[0] - _file = frappe.get_value("File", {"content_hash": self.content_hash}, ["file_url"]) + # check if a file exists with the same content hash and is also in the same folder (public or private) + _file = frappe.get_value("File", { + "content_hash": self.content_hash, + "is_private": self.is_private + }, + ["file_url"]) + if _file: self.file_url = _file file_exists = True From 2ee3610b43850d73ec1f13cc7812d4c6d3d47a34 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 19:38:14 +0530 Subject: [PATCH 012/203] fix(travis): Reduce Clutter Let's just try and do this for MariaDB Python 3.6 only --- .travis.yml | 20 -------------------- .travis/install.sh | 8 +------- .travis/run-tests.sh | 15 --------------- 3 files changed, 1 insertion(+), 42 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0dbd382702..0fec22cbe8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,31 +3,11 @@ dist: trusty sudo: required python: - - 2.7 - 3.6 -env: - - DB=mariadb - - DB=postgres - - TEST_TYPE=ui - services: - mysql -addons: - postgresql: "9.5" - hosts: - - test_site - - test_site_postgres - - test_site_ui - -matrix: - exclude: - - python: 2.7 - env: DB=postgres - - python: 2.7 - env: TEST_TYPE=ui - install: - $TRAVIS_BUILD_DIR/.travis/install.sh diff --git a/.travis/install.sh b/.travis/install.sh index a457939b22..f1a49d9ddf 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -18,10 +18,4 @@ sudo pip install -e ~/bench rm $TRAVIS_BUILD_DIR/.git/shallow cd ~/ && bench init frappe-bench --python $(which python) --frappe-path $TRAVIS_BUILD_DIR -if [[ $DB == 'mariadb' ]]; then - cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ -elif [[ $TEST_TYPE == 'ui' ]]; then - cp -r $TRAVIS_BUILD_DIR/test_sites/test_site_ui ~/frappe-bench/sites/ -elif [[ $DB == 'postgres' ]]; then - cp -r $TRAVIS_BUILD_DIR/test_sites/test_site_postgres ~/frappe-bench/sites/ -fi +cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ diff --git a/.travis/run-tests.sh b/.travis/run-tests.sh index 177e191088..439800b5aa 100755 --- a/.travis/run-tests.sh +++ b/.travis/run-tests.sh @@ -13,18 +13,3 @@ if [[ $DB == 'mariadb' ]]; then bench --site test_site reinstall --yes bench --site test_site scheduler disable bench --site test_site run-tests --coverage - -elif [[ $TEST_TYPE == 'ui' ]]; then - setup_mariadb_env 'test_site_ui' - bench --site test_site_ui reinstall --yes - bench --site test_site_ui execute frappe.utils.install.complete_setup_wizard - bench --site test_site_ui scheduler disable - cd apps/frappe && yarn && yarn cypress:run - -elif [[ $DB == 'postgres' ]]; then - psql -c "CREATE DATABASE test_frappe;" -U postgres - psql -c "CREATE USER test_frappe WITH PASSWORD 'test_frappe';" -U postgres - bench --site test_site_postgres reinstall --yes - bench --site test_site_postgres scheduler disable - bench --site test_site_postgres run-tests --coverage -fi From 6f1e6812b71fcf6122ffe7e7c5a04942bbcaecdd Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 19:41:07 +0530 Subject: [PATCH 013/203] fix(travis): Remove install.sh and run-tests.sh --- .travis.yml | 26 ++++++++++++++++++++++++-- .travis/install.sh | 21 --------------------- .travis/run-tests.sh | 15 --------------- 3 files changed, 24 insertions(+), 38 deletions(-) delete mode 100755 .travis/install.sh delete mode 100755 .travis/run-tests.sh diff --git a/.travis.yml b/.travis.yml index 0fec22cbe8..0d7e699391 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,23 @@ services: - mysql install: - - $TRAVIS_BUILD_DIR/.travis/install.sh + - sudo rm /etc/apt/sources.list.d/mongodb*.list + - sudo rm /etc/apt/sources.list.d/docker.list + - sudo apt-get install hhvm && rm -rf /home/travis/.kiex/ + - sudo apt-get purge -y mysql-common mysql-server mysql-client + - source ~/.nvm/nvm.sh + - nvm install v8.10.0 + + - pip install python-coveralls + + - wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py + + - sudo python install.py --develop --user travis --without-bench-setup + - sudo pip install -e ~/bench + + - rm $TRAVIS_BUILD_DIR/.git/shallow + - cd ~/ && bench init frappe-bench --python $(which python) --frappe-path $TRAVIS_BUILD_DIR + - cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ before_script: - cd ~/frappe-bench @@ -18,7 +34,13 @@ before_script: - sleep 10 script: - - $TRAVIS_BUILD_DIR/.travis/run-tests.sh + - mysql -u root -ptravis -e "create database test_frappe" + - mysql -u root -ptravis -e "USE mysql; CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'; FLUSH PRIVILEGES; " + - mysql -u root -ptravis -e "USE mysql; GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';" + - bench --site test_site reinstall --yes + - bench --site test_site scheduler disable + - bench --site test_site run-tests --coverage + after_script: - coveralls -b apps/frappe -d ../../sites/.coverage \ No newline at end of file diff --git a/.travis/install.sh b/.travis/install.sh deleted file mode 100755 index f1a49d9ddf..0000000000 --- a/.travis/install.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -set -e - -sudo rm /etc/apt/sources.list.d/mongodb*.list -sudo rm /etc/apt/sources.list.d/docker.list -sudo apt-get install hhvm && rm -rf /home/travis/.kiex/ -sudo apt-get purge -y mysql-common mysql-server mysql-client -source ~/.nvm/nvm.sh -nvm install v8.10.0 - -pip install python-coveralls - -wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py - -sudo python install.py --develop --user travis --without-bench-setup -sudo pip install -e ~/bench - -rm $TRAVIS_BUILD_DIR/.git/shallow -cd ~/ && bench init frappe-bench --python $(which python) --frappe-path $TRAVIS_BUILD_DIR -cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ diff --git a/.travis/run-tests.sh b/.travis/run-tests.sh deleted file mode 100755 index 439800b5aa..0000000000 --- a/.travis/run-tests.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -set -e - -setup_mariadb_env() { - mysql -u root -ptravis -e "create database $1" - mysql -u root -ptravis -e "USE mysql; CREATE USER '$1'@'localhost' IDENTIFIED BY '$1'; FLUSH PRIVILEGES; " - mysql -u root -ptravis -e "USE mysql; GRANT ALL PRIVILEGES ON \`$1\`.* TO '$1'@'localhost';" -} - -if [[ $DB == 'mariadb' ]]; then - setup_mariadb_env 'test_frappe' - bench --site test_site reinstall --yes - bench --site test_site scheduler disable - bench --site test_site run-tests --coverage From acd801ba6672b097ee931605a5efe3bf87627c63 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 19:42:36 +0530 Subject: [PATCH 014/203] fix(travis): Remove unncessary installations --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0d7e699391..9beae139cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,10 +9,6 @@ services: - mysql install: - - sudo rm /etc/apt/sources.list.d/mongodb*.list - - sudo rm /etc/apt/sources.list.d/docker.list - - sudo apt-get install hhvm && rm -rf /home/travis/.kiex/ - - sudo apt-get purge -y mysql-common mysql-server mysql-client - source ~/.nvm/nvm.sh - nvm install v8.10.0 From 87f14c2c09cf4e5844d79de84d2c1bc2f73c18eb Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 19:44:36 +0530 Subject: [PATCH 015/203] style: Use similar SQL syntax --- .travis.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9beae139cd..c6a288b2a8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,9 +30,11 @@ before_script: - sleep 10 script: - - mysql -u root -ptravis -e "create database test_frappe" - - mysql -u root -ptravis -e "USE mysql; CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'; FLUSH PRIVILEGES; " - - mysql -u root -ptravis -e "USE mysql; GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';" + - mysql -u root -ptravis -e "CREATE DATABASE test_frappe" + - mysql -u root -ptravis -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'" + - mysql -u root -ptravis -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'" + - mysql -u root -ptravis -e "FLUSH PRIVILEGES" + - bench --site test_site reinstall --yes - bench --site test_site scheduler disable - bench --site test_site run-tests --coverage From 51edbe5498f857053712d500a82505c54f695dd3 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 19:45:31 +0530 Subject: [PATCH 016/203] fix(travis): Defer installing coveralls --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c6a288b2a8..f416854120 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,6 @@ install: - source ~/.nvm/nvm.sh - nvm install v8.10.0 - - pip install python-coveralls - wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py @@ -41,4 +40,5 @@ script: after_script: + - pip install python-coveralls - coveralls -b apps/frappe -d ../../sites/.coverage \ No newline at end of file From 5406a3b26054796f719413b192bfc4f682ffbe62 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 19:48:41 +0530 Subject: [PATCH 017/203] fix(travis): Let's go pro here --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f416854120..be6fbebfcc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,10 +12,6 @@ install: - source ~/.nvm/nvm.sh - nvm install v8.10.0 - - - wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py - - - sudo python install.py --develop --user travis --without-bench-setup - sudo pip install -e ~/bench - rm $TRAVIS_BUILD_DIR/.git/shallow From f2226e0542a2d84182466b4e59bea31c61fefcef Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 19:49:16 +0530 Subject: [PATCH 018/203] fix(travis): sudo is unncessary here --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index be6fbebfcc..6d599bb1eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ install: - source ~/.nvm/nvm.sh - nvm install v8.10.0 - - sudo pip install -e ~/bench + - pip install -e ~/bench - rm $TRAVIS_BUILD_DIR/.git/shallow - cd ~/ && bench init frappe-bench --python $(which python) --frappe-path $TRAVIS_BUILD_DIR From fbd7387436b90fb0ff2faedf086642bc097d1615 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 19:51:42 +0530 Subject: [PATCH 019/203] fix(travis): Clone bench repo --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 6d599bb1eb..4ba0c967e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ install: - source ~/.nvm/nvm.sh - nvm install v8.10.0 + - git clone https://github.com/frappe/bench --depth 1 - pip install -e ~/bench - rm $TRAVIS_BUILD_DIR/.git/shallow From 6fd9c8dee62519291cc44bb2406f8d74838121c8 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 20:05:14 +0530 Subject: [PATCH 020/203] fix(travis): Do not build assets --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4ba0c967e7..65c78b7064 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,11 +12,11 @@ install: - source ~/.nvm/nvm.sh - nvm install v8.10.0 - - git clone https://github.com/frappe/bench --depth 1 - - pip install -e ~/bench + - git clone https://github.com/adityahase/bench --depth 1 --branch faster + - pip install -e ./bench - rm $TRAVIS_BUILD_DIR/.git/shallow - - cd ~/ && bench init frappe-bench --python $(which python) --frappe-path $TRAVIS_BUILD_DIR + - cd ~/ && bench init frappe-bench --skip-assets --python $(which python) --frappe-path $TRAVIS_BUILD_DIR - cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ before_script: From 515014a35556275b8eff2c41e1e1957901a67af2 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 20:06:56 +0530 Subject: [PATCH 021/203] fix(travis): Remove unncessary actions --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 65c78b7064..22c2e6c30f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,13 +15,11 @@ install: - git clone https://github.com/adityahase/bench --depth 1 --branch faster - pip install -e ./bench - - rm $TRAVIS_BUILD_DIR/.git/shallow - cd ~/ && bench init frappe-bench --skip-assets --python $(which python) --frappe-path $TRAVIS_BUILD_DIR - cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ before_script: - cd ~/frappe-bench - - sed -i 's/9000/9001/g' sites/common_site_config.json - bench start & - sleep 10 From aef7d8d3818f4c522c0605e72fe0fca361c0d93a Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 20:09:01 +0530 Subject: [PATCH 022/203] fix(travis): Rearrange commands --- .travis.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 22c2e6c30f..ecb48f4212 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,21 +18,20 @@ install: - cd ~/ && bench init frappe-bench --skip-assets --python $(which python) --frappe-path $TRAVIS_BUILD_DIR - cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ -before_script: - - cd ~/frappe-bench - - bench start & - - sleep 10 - -script: - mysql -u root -ptravis -e "CREATE DATABASE test_frappe" - mysql -u root -ptravis -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'" - mysql -u root -ptravis -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'" - mysql -u root -ptravis -e "FLUSH PRIVILEGES" +before_script: + - cd ~/frappe-bench + - bench start & + - sleep 10 - bench --site test_site reinstall --yes - bench --site test_site scheduler disable - - bench --site test_site run-tests --coverage +script: + - bench --site test_site run-tests --coverage after_script: - pip install python-coveralls From 550f93bdd4b9b3c0a7bfdded51f45faa5ca73b36 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 20:19:42 +0530 Subject: [PATCH 023/203] fix(travis): Change working directory to ~ --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ecb48f4212..5aad3d295f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,13 +9,14 @@ services: - mysql install: + - cd ~ - source ~/.nvm/nvm.sh - nvm install v8.10.0 - git clone https://github.com/adityahase/bench --depth 1 --branch faster - pip install -e ./bench - - cd ~/ && bench init frappe-bench --skip-assets --python $(which python) --frappe-path $TRAVIS_BUILD_DIR + - bench init frappe-bench --skip-assets --python $(which python) --frappe-path $TRAVIS_BUILD_DIR - cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ - mysql -u root -ptravis -e "CREATE DATABASE test_frappe" From cbcb9de2d12085075f98798880c082cbad5b49fe Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 20:28:06 +0530 Subject: [PATCH 024/203] fix(travis): Drop ~ in favour of . --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5aad3d295f..1b78bb6f48 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ services: install: - cd ~ - - source ~/.nvm/nvm.sh + - source ./.nvm/nvm.sh - nvm install v8.10.0 - git clone https://github.com/adityahase/bench --depth 1 --branch faster @@ -25,7 +25,7 @@ install: - mysql -u root -ptravis -e "FLUSH PRIVILEGES" before_script: - - cd ~/frappe-bench + - cd ./frappe-bench - bench start & - sleep 10 - bench --site test_site reinstall --yes From 265c215fa8d9a75258ded30b7dea09bb56c4b29b Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 20:33:31 +0530 Subject: [PATCH 025/203] fix(travis): Set mysql root password to 'travis' --- .travis.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1b78bb6f48..7a499de2d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,10 +19,12 @@ install: - bench init frappe-bench --skip-assets --python $(which python) --frappe-path $TRAVIS_BUILD_DIR - cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ - - mysql -u root -ptravis -e "CREATE DATABASE test_frappe" - - mysql -u root -ptravis -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'" - - mysql -u root -ptravis -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'" - - mysql -u root -ptravis -e "FLUSH PRIVILEGES" + - mysql -u root -e "CREATE DATABASE test_frappe" + - mysql -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'" + - mysql -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'" + + - mysql -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'" + - mysql -u root -e "FLUSH PRIVILEGES" before_script: - cd ./frappe-bench From fac983270d41e46d369d517ef9cee57c2674f46c Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 20:34:26 +0530 Subject: [PATCH 026/203] fix(travis): SET required MariaDB variables --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index 7a499de2d6..40f7cd8e7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,12 @@ install: - bench init frappe-bench --skip-assets --python $(which python) --frappe-path $TRAVIS_BUILD_DIR - cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ + - mysql -u root -e "SET GLOBAL character_set_server = 'utf8mb4'" + - mysql -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'" + - mysql -u root -e "SET GLOBAL innodb_file_format=Barracuda" + - mysql -u root -e "SET GLOBAL innodb_file_per_table=ON" + - mysql -u root -e "SET GLOBAL innodb_large_prefix=1" + - mysql -u root -e "CREATE DATABASE test_frappe" - mysql -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'" - mysql -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'" From a8b73c0a048ac887373d84eb13d3bdd22791ec5c Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 20:46:25 +0530 Subject: [PATCH 027/203] fix(travis): Do not run unnecessary processes --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 40f7cd8e7a..42b022c588 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,6 +34,10 @@ install: before_script: - cd ./frappe-bench + - sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile + - sed -i 's/socketio:/# socketio:/g' Procfile + - sed -i 's/watch:/# watch:/g' Procfile + - sed -i 's/schedule:/# schedule:/g' Procfile - bench start & - sleep 10 - bench --site test_site reinstall --yes From dcd4aac0849abd2b0e254d76a3f9e380b0bd1d6e Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 20:53:17 +0530 Subject: [PATCH 028/203] fix(travis): Do not sleep for 10 seconds --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 42b022c588..f6e3752b5a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,12 +34,13 @@ install: before_script: - cd ./frappe-bench + - sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile - sed -i 's/socketio:/# socketio:/g' Procfile - sed -i 's/watch:/# watch:/g' Procfile - sed -i 's/schedule:/# schedule:/g' Procfile + - bench start & - - sleep 10 - bench --site test_site reinstall --yes - bench --site test_site scheduler disable From a09e2794bf1f6d35655b9003433b94d652aeaee2 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 20:57:32 +0530 Subject: [PATCH 029/203] fix(travis): Install wkhtmltopdf --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index f6e3752b5a..22e34033d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,11 @@ install: - mysql -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'" - mysql -u root -e "FLUSH PRIVILEGES" + - wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz + - tar -xf /tmp/wkhtmltox.tar.xz -C /tmp + - sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf + - sudo chmod o+x /usr/local/bin/wkhtmltopdf + before_script: - cd ./frappe-bench From 9c4567018ea188fa44ec037b7efb6f983ee8f413 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 20:57:49 +0530 Subject: [PATCH 030/203] fix(travis): Build frappe assets --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 22e34033d6..323a9f68dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,6 +48,7 @@ before_script: - bench start & - bench --site test_site reinstall --yes - bench --site test_site scheduler disable + - bench build --app frappe script: - bench --site test_site run-tests --coverage From 2941568fe2b38603d303195f5d41460ce672d429 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 21:01:03 +0530 Subject: [PATCH 031/203] perf(travis): Use pip cacher --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 323a9f68dd..86f9e52fa3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,9 @@ python: services: - mysql +cache: + - pip + install: - cd ~ - source ./.nvm/nvm.sh From eded71a63a3f8413d773fcb337c2a337058b1666 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 21:08:58 +0530 Subject: [PATCH 032/203] fix(travis): Add test_site to hosts --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 86f9e52fa3..0ec9ee5349 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,10 @@ python: services: - mysql +addons: + hosts: + - test_site + cache: - pip From 043260cf21eaf63b8e18cfb04498c4dc57ce095a Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 21:12:13 +0530 Subject: [PATCH 033/203] perf(travis): Use --depth 1 while performing git clone --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0ec9ee5349..bc4d4beb8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,9 @@ addons: hosts: - test_site +git: + depth: 1 + cache: - pip From 690ef1af7cebf9c44f7a6a15e55b32ae20ced609 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 21:19:35 +0530 Subject: [PATCH 034/203] fix(travis): Run Python 2.7 and Python 3.6 builds --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index bc4d4beb8a..317cff12e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ sudo: required python: - 3.6 + - 2.7 services: - mysql From a4cc151f1fcf56745817de36d14c2dbceee6f7e0 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 21:44:12 +0530 Subject: [PATCH 035/203] fix(travis): Use MariaDB 10.3 instead of MySQL --- .travis.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 317cff12e2..c9b8249c04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,12 +6,10 @@ python: - 3.6 - 2.7 -services: - - mysql - addons: hosts: - test_site + mariadb: 10.3 git: depth: 1 @@ -32,9 +30,6 @@ install: - mysql -u root -e "SET GLOBAL character_set_server = 'utf8mb4'" - mysql -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'" - - mysql -u root -e "SET GLOBAL innodb_file_format=Barracuda" - - mysql -u root -e "SET GLOBAL innodb_file_per_table=ON" - - mysql -u root -e "SET GLOBAL innodb_large_prefix=1" - mysql -u root -e "CREATE DATABASE test_frappe" - mysql -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'" From f064689af096f53866e3339df65d8f7e3db0055f Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 21:57:54 +0530 Subject: [PATCH 036/203] fix(travis): Create Build Matrix --- .travis.yml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index c9b8249c04..7f9c76cf24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,14 +2,11 @@ language: python dist: trusty sudo: required -python: - - 3.6 - - 2.7 - addons: hosts: - test_site mariadb: 10.3 + postgresql: 9.5 git: depth: 1 @@ -17,6 +14,21 @@ git: cache: - pip +matrix: + include: + - name: "Python 3.6 MariaDB" + python: 3.6 + env: DB=MariaDB + - name: "Python 3.6 PostgreSQL" + python: 3.6 + env: DB=PostgreSQL + - name: "Cypress" + python: 3.6 + env: DB=MariaDB + - name: "Python 2.7 MariaDB" + python: 2.7 + env: DB=MariaDB + install: - cd ~ - source ./.nvm/nvm.sh From 96d7a2f171fd4861273de87c6a4f6b45bc7977c2 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 23 Jul 2019 17:38:16 +0530 Subject: [PATCH 037/203] fix(travis): Handle postgres builds as well --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 7f9c76cf24..f5899e3bd6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,6 +50,9 @@ install: - mysql -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'" - mysql -u root -e "FLUSH PRIVILEGES" + - psql -c "CREATE DATABASE test_frappe" -U postgres + - psql -c "CREATE USER test_frappe WITH PASSWORD 'test_frappe'" -U postgres + - wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz - tar -xf /tmp/wkhtmltox.tar.xz -C /tmp - sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf From fb03b34f3dfeca9606b6e2b7a2ecdfc19c059f2d Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 23 Jul 2019 17:44:52 +0530 Subject: [PATCH 038/203] fix(travis): Use database specific site_config.json files --- .travis.yml | 12 +++++++----- test_sites/mariadb.json | 14 ++++++++++++++ test_sites/postgres.json | 14 ++++++++++++++ 3 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 test_sites/mariadb.json create mode 100644 test_sites/postgres.json diff --git a/.travis.yml b/.travis.yml index f5899e3bd6..b8a1b30f65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,16 +18,16 @@ matrix: include: - name: "Python 3.6 MariaDB" python: 3.6 - env: DB=MariaDB + env: DB=mariadb - name: "Python 3.6 PostgreSQL" python: 3.6 - env: DB=PostgreSQL + env: DB=postgres - name: "Cypress" python: 3.6 - env: DB=MariaDB + env: DB=mariadb - name: "Python 2.7 MariaDB" python: 2.7 - env: DB=MariaDB + env: DB=mariadb install: - cd ~ @@ -38,7 +38,9 @@ install: - pip install -e ./bench - bench init frappe-bench --skip-assets --python $(which python) --frappe-path $TRAVIS_BUILD_DIR - - cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ + + - mkdir ~/frappe-bench/sites/test_site + - cp $TRAVIS_BUILD_DIR/test_sites/$DB.json ~/frappe-bench/sites/test_site/site_config.json - mysql -u root -e "SET GLOBAL character_set_server = 'utf8mb4'" - mysql -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'" diff --git a/test_sites/mariadb.json b/test_sites/mariadb.json new file mode 100644 index 0000000000..550ad94769 --- /dev/null +++ b/test_sites/mariadb.json @@ -0,0 +1,14 @@ +{ + "db_host": "localhost", + "db_name": "test_frappe", + "db_password": "test_frappe", + "db_type": "mariadb", + "auto_email_id": "test@example.com", + "mail_server": "smtp.example.com", + "mail_login": "test@example.com", + "mail_password": "test", + "admin_password": "admin", + "root_login": "root", + "root_password": "travis", + "host_name": "http://test_site:8000" +} diff --git a/test_sites/postgres.json b/test_sites/postgres.json new file mode 100644 index 0000000000..619dd91f10 --- /dev/null +++ b/test_sites/postgres.json @@ -0,0 +1,14 @@ +{ + "db_host": "localhost", + "db_name": "test_frappe", + "db_password": "test_frappe", + "db_type": "postgres", + "auto_email_id": "test@example.com", + "mail_server": "smtp.example.com", + "mail_login": "test@example.com", + "mail_password": "test", + "admin_password": "admin", + "root_login": "postgres", + "root_password": "travis", + "host_name": "http://test_site:8000" +} From 252b14c7f1af3251191a0a1327259781f0cacb29 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 23 Jul 2019 18:03:17 +0530 Subject: [PATCH 039/203] fix(travis): Include UI tests --- .travis.yml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index b8a1b30f65..32f409babd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,16 +18,16 @@ matrix: include: - name: "Python 3.6 MariaDB" python: 3.6 - env: DB=mariadb + env: DB=mariadb TYPE=server - name: "Python 3.6 PostgreSQL" python: 3.6 - env: DB=postgres + env: DB=postgres TYPE=server - name: "Cypress" python: 3.6 - env: DB=mariadb + env: DB=mariadb TYPE=ui - name: "Python 2.7 MariaDB" python: 2.7 - env: DB=mariadb + env: DB=mariadb TYPE=server install: - cd ~ @@ -74,7 +74,14 @@ before_script: - bench build --app frappe script: - - bench --site test_site run-tests --coverage + - | + if [ $TYPE == "server" ]; then + bench --site test_site run-tests --coverage + else + bench setup requirements --node + bench --site test_site execute frappe.utils.install.complete_setup_wizard + bench --site test_site run-ui-tests + fi after_script: - pip install python-coveralls From 393e3c2165be944baf3db29d096c4c6a7801d50a Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 23 Jul 2019 18:25:20 +0530 Subject: [PATCH 040/203] perf(travis): Use yarn cacher --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 32f409babd..227204e139 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ git: cache: - pip + - yarn matrix: include: From 6a2660a34c8c0f33c3eb24c833f255434c8f0b9d Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 23 Jul 2019 18:34:22 +0530 Subject: [PATCH 041/203] fix(travis): Remove shell scripted if condition --- .travis.yml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 227204e139..97ee96b36e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,15 +20,25 @@ matrix: - name: "Python 3.6 MariaDB" python: 3.6 env: DB=mariadb TYPE=server + script: bench --site test_site run-tests --coverage + - name: "Python 3.6 PostgreSQL" python: 3.6 env: DB=postgres TYPE=server + script: bench --site test_site run-tests --coverage + - name: "Cypress" python: 3.6 env: DB=mariadb TYPE=ui + before_script: + - bench setup requirements --node + - bench --site test_site execute frappe.utils.install.complete_setup_wizard + script: bench --site test_site run-ui-tests frappe + - name: "Python 2.7 MariaDB" python: 2.7 env: DB=mariadb TYPE=server + script: bench --site test_site run-tests --coverage install: - cd ~ @@ -61,7 +71,7 @@ install: - sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf - sudo chmod o+x /usr/local/bin/wkhtmltopdf -before_script: +after_install: - cd ./frappe-bench - sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile @@ -74,16 +84,7 @@ before_script: - bench --site test_site scheduler disable - bench build --app frappe -script: - - | - if [ $TYPE == "server" ]; then - bench --site test_site run-tests --coverage - else - bench setup requirements --node - bench --site test_site execute frappe.utils.install.complete_setup_wizard - bench --site test_site run-ui-tests - fi after_script: - pip install python-coveralls - - coveralls -b apps/frappe -d ../../sites/.coverage \ No newline at end of file + - coveralls -b apps/frappe -d ../../sites/.coverage From ed4cd9641eac97f335468e2667fe20da3680382b Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 23 Jul 2019 18:39:32 +0530 Subject: [PATCH 042/203] fix(travis): Reach bench directory before executing bench commands --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 97ee96b36e..cd282dd0ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,17 +20,18 @@ matrix: - name: "Python 3.6 MariaDB" python: 3.6 env: DB=mariadb TYPE=server - script: bench --site test_site run-tests --coverage + script: cd ~/frappe-bench && bench --site test_site run-tests --coverage - name: "Python 3.6 PostgreSQL" python: 3.6 env: DB=postgres TYPE=server - script: bench --site test_site run-tests --coverage + script: cd ~/frappe-bench && bench --site test_site run-tests --coverage - name: "Cypress" python: 3.6 env: DB=mariadb TYPE=ui before_script: + - cd ~/frappe-bench - bench setup requirements --node - bench --site test_site execute frappe.utils.install.complete_setup_wizard script: bench --site test_site run-ui-tests frappe @@ -38,7 +39,7 @@ matrix: - name: "Python 2.7 MariaDB" python: 2.7 env: DB=mariadb TYPE=server - script: bench --site test_site run-tests --coverage + script: cd ~/frappe-bench && bench --site test_site run-tests --coverage install: - cd ~ From cb89a3062f985eb3ccc72fdac588f29a3f0debc2 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 23 Jul 2019 18:41:58 +0530 Subject: [PATCH 043/203] fix(travis): Remove TYPE environment vairable --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index cd282dd0ef..8c870c606d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,17 +19,17 @@ matrix: include: - name: "Python 3.6 MariaDB" python: 3.6 - env: DB=mariadb TYPE=server + env: DB=mariadb script: cd ~/frappe-bench && bench --site test_site run-tests --coverage - name: "Python 3.6 PostgreSQL" python: 3.6 - env: DB=postgres TYPE=server + env: DB=postgres script: cd ~/frappe-bench && bench --site test_site run-tests --coverage - name: "Cypress" python: 3.6 - env: DB=mariadb TYPE=ui + env: DB=mariadb before_script: - cd ~/frappe-bench - bench setup requirements --node @@ -38,7 +38,7 @@ matrix: - name: "Python 2.7 MariaDB" python: 2.7 - env: DB=mariadb TYPE=server + env: DB=mariadb script: cd ~/frappe-bench && bench --site test_site run-tests --coverage install: From e80639cb667ac4753758e5cb940708ce2be8960f Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 23 Jul 2019 18:45:20 +0530 Subject: [PATCH 044/203] fix(travis): No stage named after_install --- .travis.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8c870c606d..f161291b0e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,18 +20,17 @@ matrix: - name: "Python 3.6 MariaDB" python: 3.6 env: DB=mariadb - script: cd ~/frappe-bench && bench --site test_site run-tests --coverage + script: bench --site test_site run-tests --coverage - name: "Python 3.6 PostgreSQL" python: 3.6 env: DB=postgres - script: cd ~/frappe-bench && bench --site test_site run-tests --coverage + script: bench --site test_site run-tests --coverage - name: "Cypress" python: 3.6 env: DB=mariadb before_script: - - cd ~/frappe-bench - bench setup requirements --node - bench --site test_site execute frappe.utils.install.complete_setup_wizard script: bench --site test_site run-ui-tests frappe @@ -39,7 +38,7 @@ matrix: - name: "Python 2.7 MariaDB" python: 2.7 env: DB=mariadb - script: cd ~/frappe-bench && bench --site test_site run-tests --coverage + script: bench --site test_site run-tests --coverage install: - cd ~ @@ -72,7 +71,6 @@ install: - sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf - sudo chmod o+x /usr/local/bin/wkhtmltopdf -after_install: - cd ./frappe-bench - sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile @@ -85,7 +83,6 @@ after_install: - bench --site test_site scheduler disable - bench build --app frappe - after_script: - pip install python-coveralls - coveralls -b apps/frappe -d ../../sites/.coverage From 8d3d4c016baa8e2e81f21930c800dfa686004a42 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 23 Jul 2019 18:50:28 +0530 Subject: [PATCH 045/203] perf(travis): Use npm cacher --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f161291b0e..12c3e6cc24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ git: cache: - pip + - npm - yarn matrix: From c774a6fc51b020bd47b190302658c448ae9f3f91 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 23 Jul 2019 18:51:39 +0530 Subject: [PATCH 046/203] fix(travis): Run cypress tests headlessly --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 12c3e6cc24..f7ef23f080 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,7 @@ matrix: before_script: - bench setup requirements --node - bench --site test_site execute frappe.utils.install.complete_setup_wizard - script: bench --site test_site run-ui-tests frappe + script: bench --site test_site run-ui-tests frappe --headless - name: "Python 2.7 MariaDB" python: 2.7 From 0204dae52d1861dedbc56baeadbc9918ad1a6789 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 23 Jul 2019 19:05:22 +0530 Subject: [PATCH 047/203] fix(travis): execute bench build before cypress tests --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f7ef23f080..6ee1d541d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,6 +33,7 @@ matrix: env: DB=mariadb before_script: - bench setup requirements --node + - bench build --app frappe - bench --site test_site execute frappe.utils.install.complete_setup_wizard script: bench --site test_site run-ui-tests frappe --headless From 8a3c5a8c51107213970b03fbf036fe9704e5c3ad Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 23 Jul 2019 19:08:04 +0530 Subject: [PATCH 048/203] fix(tests): Raise CalledProcessError if run-ui-test errors --- frappe/commands/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 6e0786a528..eb8cf0e088 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -478,7 +478,7 @@ def run_ui_tests(context, app, headless=False): run_or_open = 'run' if headless else 'open' command = '{site_env} {password_env} yarn run cypress {run_or_open}' formatted_command = command.format(site_env=site_env, password_env=password_env, run_or_open=run_or_open) - frappe.commands.popen(formatted_command, cwd=app_base_path) + frappe.commands.popen(formatted_command, cwd=app_base_path, raise_err=True) @click.command('run-setup-wizard-ui-test') @click.option('--app', help="App to run tests on, leave blank for all apps") From ab46cd21bbe45da45ff95f333037c07bf65f915d Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 23 Jul 2019 19:15:02 +0530 Subject: [PATCH 049/203] fix(travis): Remove unused files --- .travis.yml | 2 +- {test_sites => .travis}/mariadb.json | 0 {test_sites => .travis}/postgres.json | 0 test_sites/apps.txt | 0 test_sites/test_site/site_config.json | 12 ------------ test_sites/test_site_postgres/site_config.json | 15 --------------- test_sites/test_site_ui/site_config.json | 14 -------------- 7 files changed, 1 insertion(+), 42 deletions(-) rename {test_sites => .travis}/mariadb.json (100%) rename {test_sites => .travis}/postgres.json (100%) delete mode 100644 test_sites/apps.txt delete mode 100644 test_sites/test_site/site_config.json delete mode 100644 test_sites/test_site_postgres/site_config.json delete mode 100644 test_sites/test_site_ui/site_config.json diff --git a/.travis.yml b/.travis.yml index 6ee1d541d4..200d09365c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,7 +53,7 @@ install: - bench init frappe-bench --skip-assets --python $(which python) --frappe-path $TRAVIS_BUILD_DIR - mkdir ~/frappe-bench/sites/test_site - - cp $TRAVIS_BUILD_DIR/test_sites/$DB.json ~/frappe-bench/sites/test_site/site_config.json + - cp $TRAVIS_BUILD_DIR/.travis/$DB.json ~/frappe-bench/sites/test_site/site_config.json - mysql -u root -e "SET GLOBAL character_set_server = 'utf8mb4'" - mysql -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'" diff --git a/test_sites/mariadb.json b/.travis/mariadb.json similarity index 100% rename from test_sites/mariadb.json rename to .travis/mariadb.json diff --git a/test_sites/postgres.json b/.travis/postgres.json similarity index 100% rename from test_sites/postgres.json rename to .travis/postgres.json diff --git a/test_sites/apps.txt b/test_sites/apps.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test_sites/test_site/site_config.json b/test_sites/test_site/site_config.json deleted file mode 100644 index 40a2d62b6d..0000000000 --- a/test_sites/test_site/site_config.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "test_frappe", - "db_password": "test_frappe", - "auto_email_id": "test@example.com", - "mail_server": "smtp.example.com", - "mail_login": "test@example.com", - "mail_password": "test", - "admin_password": "admin", - "root_password": "travis", - "run_selenium_tests": 1, - "host_name": "http://test_site:8000" -} diff --git a/test_sites/test_site_postgres/site_config.json b/test_sites/test_site_postgres/site_config.json deleted file mode 100644 index 5f00580c53..0000000000 --- a/test_sites/test_site_postgres/site_config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_host": "localhost", - "db_name": "test_frappe", - "db_password": "test_frappe", - "db_type": "postgres", - "auto_email_id": "test@example.com", - "mail_server": "smtp.example.com", - "mail_login": "test@example.com", - "mail_password": "test", - "admin_password": "admin", - "root_login": "postgres", - "root_password": "travis", - "run_selenium_tests": 1, - "host_name": "http://test_site_postgres:8000" -} diff --git a/test_sites/test_site_ui/site_config.json b/test_sites/test_site_ui/site_config.json deleted file mode 100644 index d233a6b553..0000000000 --- a/test_sites/test_site_ui/site_config.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "developer_mode": 1, - "db_name": "test_site_ui", - "db_password": "test_site_ui", - "db_type": "mariadb", - "auto_email_id": "test@example.com", - "mail_server": "smtp.example.com", - "mail_login": "test@example.com", - "mail_password": "test", - "admin_password": "admin", - "root_password": "travis", - "run_selenium_tests": 1, - "host_name": "http://test_site_ui:8000" -} From 3623e4735f949147be07b4302b0786702561bcea Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 23 Jul 2019 19:46:52 +0530 Subject: [PATCH 050/203] perf(travis): Faster cypress tests --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 200d09365c..bdbea5e551 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,10 +32,11 @@ matrix: python: 3.6 env: DB=mariadb before_script: - - bench setup requirements --node - - bench build --app frappe - bench --site test_site execute frappe.utils.install.complete_setup_wizard - script: bench --site test_site run-ui-tests frappe --headless + - cd apps/frappe + - yarn + script: + - yarn cypress:run - name: "Python 2.7 MariaDB" python: 2.7 From cf99fd81f3c5f6e09ed3bed3c8b8beb0e42a167c Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 24 Jul 2019 13:44:32 +0530 Subject: [PATCH 051/203] fix(travis): Activate socketio --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index bdbea5e551..195dfa9636 100644 --- a/.travis.yml +++ b/.travis.yml @@ -76,8 +76,6 @@ install: - cd ./frappe-bench - - sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile - - sed -i 's/socketio:/# socketio:/g' Procfile - sed -i 's/watch:/# watch:/g' Procfile - sed -i 's/schedule:/# schedule:/g' Procfile From d11a2e5bcda70e4c6b0135f5e8339d3692c3c468 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 24 Jul 2019 13:49:28 +0530 Subject: [PATCH 052/203] fix(travis): No need to disable scheduler with cli --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 195dfa9636..ac1905671f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -81,7 +81,6 @@ install: - bench start & - bench --site test_site reinstall --yes - - bench --site test_site scheduler disable - bench build --app frappe after_script: From bf1c38380b4f5d3e8394f67b3a12757dce2111e1 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 24 Jul 2019 13:59:47 +0530 Subject: [PATCH 053/203] fix(travis): Build app before cypress tests --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index ac1905671f..0f92947f9f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,10 +33,10 @@ matrix: env: DB=mariadb before_script: - bench --site test_site execute frappe.utils.install.complete_setup_wizard - - cd apps/frappe - - yarn + - bench setup requirements --node + - bench build --app frappe script: - - yarn cypress:run + - bench --site test_site run-ui-tests frappe --headless - name: "Python 2.7 MariaDB" python: 2.7 From ecf3dfeb150a196a3804a7ccfe915576a232e63b Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 24 Jul 2019 14:11:58 +0530 Subject: [PATCH 054/203] fix(travis): Run setup requirements before running node socketio.js --- .travis.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0f92947f9f..7abad59004 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,27 +20,23 @@ matrix: include: - name: "Python 3.6 MariaDB" python: 3.6 - env: DB=mariadb + env: DB=mariadb TYPE=server script: bench --site test_site run-tests --coverage - name: "Python 3.6 PostgreSQL" python: 3.6 - env: DB=postgres + env: DB=postgres TYPE=server script: bench --site test_site run-tests --coverage - name: "Cypress" python: 3.6 - env: DB=mariadb - before_script: - - bench --site test_site execute frappe.utils.install.complete_setup_wizard - - bench setup requirements --node - - bench build --app frappe - script: - - bench --site test_site run-ui-tests frappe --headless + env: DB=mariadb TYPE=ui + before_script: bench --site test_site execute frappe.utils.install.complete_setup_wizard + script: bench --site test_site run-ui-tests frappe --headless - name: "Python 2.7 MariaDB" python: 2.7 - env: DB=mariadb + env: DB=mariadb TYPE=server script: bench --site test_site run-tests --coverage install: @@ -79,6 +75,11 @@ install: - sed -i 's/watch:/# watch:/g' Procfile - sed -i 's/schedule:/# schedule:/g' Procfile + - if [ $TYPE == "server" ]; then sed -i 's/socketio:/# socketio:/g' Procfile; fi + - if [ $TYPE == "server" ]; then sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile; fi + + - if [ $TYPE == "ui" ]; then bench setup requirements --node; fi + - bench start & - bench --site test_site reinstall --yes - bench build --app frappe From 7d65a557d1eb813de0ebe244554f698c8805d81c Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 24 Jul 2019 14:29:29 +0530 Subject: [PATCH 055/203] fix(commnds): Raise CalledProcessError if process returns non-zero exit code --- frappe/commands/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/commands/__init__.py b/frappe/commands/__init__.py index 84e5f4937f..8110f2ec19 100644 --- a/frappe/commands/__init__.py +++ b/frappe/commands/__init__.py @@ -62,7 +62,7 @@ def popen(command, *args, **kwargs): return_ = proc.wait() - if raise_err: + if return_ and raise_err: raise subprocess.CalledProcessError(return_, command) return return_ From 8158803fba9df317a95acae39ae1cbb3f7c7ac01 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 24 Jul 2019 15:00:36 +0530 Subject: [PATCH 056/203] fix(travis): Use frappe/bench --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7abad59004..df66db88a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ install: - source ./.nvm/nvm.sh - nvm install v8.10.0 - - git clone https://github.com/adityahase/bench --depth 1 --branch faster + - git clone https://github.com/frappe/bench --depth 1 - pip install -e ./bench - bench init frappe-bench --skip-assets --python $(which python) --frappe-path $TRAVIS_BUILD_DIR From 80000353e8dde3fa06f716c456077ede767008fe Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 24 Jul 2019 15:07:41 +0530 Subject: [PATCH 057/203] feat: google calendar sync --- frappe/hooks.py | 5 + .../data_migration_mapping/__init__.py | 0 .../google_calendar/google_calendar.js | 31 ++ .../google_calendar/google_calendar.json | 8 +- .../google_calendar/google_calendar.py | 427 ++++++++++-------- 5 files changed, 264 insertions(+), 207 deletions(-) delete mode 100644 frappe/integrations/data_migration_mapping/__init__.py diff --git a/frappe/hooks.py b/frappe/hooks.py index 6b9caec22a..253dad0543 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -139,6 +139,11 @@ doc_events = { }, "Email Group Member": { "validate": "frappe.email.doctype.email_group.email_group.restrict_email_group" + }, + "Event": { + "after_insert": "frappe.integrations.doctype.google_calendar.google_calendar.insert_events", + "on_update": "frappe.integrations.doctype.google_calendar.google_calendar.update_events", + "on_trash": "frappe.integrations.doctype.google_calendar.google_calendar.delete_events", } } diff --git a/frappe/integrations/data_migration_mapping/__init__.py b/frappe/integrations/data_migration_mapping/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.js b/frappe/integrations/doctype/google_calendar/google_calendar.js index 905568df39..8a903888db 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.js +++ b/frappe/integrations/doctype/google_calendar/google_calendar.js @@ -2,6 +2,37 @@ // For license information, please see license.txt frappe.ui.form.on('Google Calendar', { + refresh: function(frm) { + frm.set_value("user", frappe.session.user); + + frappe.realtime.on('import_google_calendar', (data) => { + if (data.progress) { + frm.dashboard.show_progress('Syncing Google Calendar', data.progress / data.total * 100, + __('Syncing {0} of {1}', [data.progress, data.total])); + if (data.progress === data.total) { + frm.dashboard.hide_progress('Sync Google Calendar'); + } + } + }); + + if (frm.doc.refresh_token) { + frm.add_custom_button(__('Sync Calendar'), function () { + frappe.show_alert({ + indicator: 'green', + message: __('Syncing') + }); + frappe.call({ + method: "frappe.integrations.doctype.google_calendar.google_calendar.sync", + args: { + "g_contact": frm.doc.name + }, + }).then((r) => { + frappe.hide_progress(); + frappe.msgprint(r.message); + }); + }); + } + }, authorize_google_calendar_access: function(frm) { let reauthorize = 0; if(frm.doc.authorization_code) { diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.json b/frappe/integrations/doctype/google_calendar/google_calendar.json index 9234e26f9f..e001317956 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.json +++ b/frappe/integrations/doctype/google_calendar/google_calendar.json @@ -16,7 +16,6 @@ "refresh_token", "authorization_code", "column_break_6", - "session_token", "google_calendar_id", "next_sync_token" ], @@ -72,11 +71,6 @@ "fieldname": "column_break_6", "fieldtype": "Column Break" }, - { - "fieldname": "session_token", - "fieldtype": "Data", - "label": "Session Token" - }, { "fieldname": "next_sync_token", "fieldtype": "Data", @@ -100,7 +94,7 @@ "label": "Authorize Google Calendar Access" } ], - "modified": "2019-07-20 22:28:32.989673", + "modified": "2019-07-24 15:02:00.233602", "modified_by": "Administrator", "module": "Integrations", "name": "Google Calendar", diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index b4ff9b9ecf..c3aa7254d3 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -5,11 +5,15 @@ from __future__ import unicode_literals import frappe import requests +import googleapiclient.discovery +import google.oauth2.credentials +import time from frappe import _ from frappe.model.document import Document from frappe.utils import get_request_site_address +from googleapiclient.errors import HttpError -SCOPES = 'https://www.googleapis.com/auth/calendar/v3' +SCOPES = "https://www.googleapis.com/auth/calendar/v3" class GoogleCalendar(Document): @@ -24,7 +28,7 @@ class GoogleCalendar(Document): frappe.throw(_("Google Calendar Integration is disabled.")) if not self.refresh_token: - button_label = frappe.bold(_('Allow Google Calendar Access')) + button_label = frappe.bold(_("Allow Google Calendar Access")) raise frappe.ValidationError(_("Click on {0} to generate Refresh Token.").format(button_label)) data = { @@ -38,7 +42,7 @@ class GoogleCalendar(Document): try: r = requests.post("https://www.googleapis.com/oauth2/v4/token", data=data).json() except requests.exceptions.HTTPError: - button_label = frappe.bold(_('Allow Google Calendar Access')) + button_label = frappe.bold(_("Allow Google Calendar Access")) frappe.throw(_("Something went wrong during the token generation. Click on {0} to generate a new one.").format(button_label)) return r.get("access_token") @@ -96,239 +100,262 @@ def google_callback(client_id=None, redirect_uri=None, code=None): authorize_access(google_calendar) -class GoogleCalendarSync(): - def __init__(self, g_calendar): - google_settings = frappe.get_doc("Google Settings") +@frappe.whitelist() +def sync(g_calendar=None): + filters = {"enable": 1} - g_calendar = frappe.get_doc("Google Calendar", g_calendar) + if g_calendar: + filters.update({"name": g_calendar}) - credentials_dict = { - 'refresh_token': g_calendar.refresh_token, - 'token_uri': 'https://www.googleapis.com/oauth2/v4/token', - 'client_id': google_settings.client_id, - 'client_secret': google_settings.get_password(fieldname='client_secret', raise_exception=False), - 'scopes':'https://www.googleapis.com/auth/calendar' - } + google_calendars = frappe.get_list("Google Calendar", filters=filters) - credentials = google.oauth2.credentials.Credentials(**credentials_dict) - google_calendar = googleapiclient.discovery.build('calendar', 'v3', credentials=credentials) + for g in google_calendars: + get_events(frappe.get_doc("Google Calendar", g.name)) - self.check_remote_calendar() +def get_credentials(g_calendar): + google_settings = frappe.get_doc("Google Settings") + account = frappe.get_doc("Google Calendar", g_calendar) - def check_remote_calendar(self): - def _create_calendar(): - timezone = frappe.db.get_value("System Settings", None, "time_zone") - calendar = { - 'summary': self.account.calendar_name, - 'timeZone': timezone - } + credentials_dict = { + "token": account.get_access_token(), + "refresh_token": account.refresh_token, + "token_uri": "https://www.googleapis.com/oauth2/v4/token", + "client_id": google_settings.client_id, + "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), + "scopes":"https://www.googleapis.com/auth/calendar" + } + + credentials = google.oauth2.credentials.Credentials(**credentials_dict) + google_calendar = googleapiclient.discovery.build("calendar", "v3", credentials=credentials) + + check_remote_calendar(account, google_calendar) + + return google_calendar, account + +def check_remote_calendar(account, google_calendar): + def _create_calendar(): + calendar = { + "summary": account.calendar_name, + "timeZone": frappe.db.get_value("System Settings", None, "time_zone") + } + try: + created_calendar = google_calendar.calendars().insert(body=calendar).execute() + frappe.db.set_value("Google Calendar", account.name, "google_calendar_id", created_calendar["id"]) + frappe.db.commit() + except Exception: + frappe.log_error(frappe.get_traceback()) + + try: + if not account.google_calendar_id: try: - created_calendar = self.gcalendar.calendars().insert(body=calendar).execute() - frappe.db.set_value("GCalendar Account", self.account.name, "gcalendar_id", created_calendar["id"]) + google_calendar.calendars().get(calendarId=account.google_calendar_id).execute() except Exception: frappe.log_error(frappe.get_traceback()) + else: + _create_calendar() + except HttpError as err: + if err.resp.status in [403, 500, 503]: + time.sleep(5) + elif err.resp.status in [404]: + _create_calendar() + else: raise +def get_events(doc, method=None, page_length=10): + """ + Sync Events with Google Calendar + """ + google_calendar, account = get_credentials(doc.name) + page_token = None + results = [] + events = { + "items": [] + } + + while True: try: - if self.account.gcalendar_id is not None: - try: - self.gcalendar.calendars().get(calendarId=self.account.gcalendar_id).execute() - except Exception: - frappe.log_error(frappe.get_traceback()) - else: - _create_calendar() + events = self.google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, + singleEvents=False, showDeleted=True, syncToken=account.next_sync_token or None).execute() except HttpError as err: - if err.resp.status in [403, 500, 503]: - time.sleep(5) - elif err.resp.status in [404]: - _create_calendar() - else: raise + if err.resp.status in [410]: + events = self.google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, + singleEvents=False, showDeleted=True, timeMin=add_years(None, -1).strftime("%Y-%m-%dT%H:%M:%SZ")).execute() + else: + frappe.log_error(err.resp, "Google Calendar Events Fetch Error.") + for event in events["items"]: + event.update({"account": account.name}) + event.update({"calendar_tz": events["timeZone"]}) + results.append(event) + page_token = events.get("nextPageToken") - def get(self, remote_objectname, fields=None, filters=None, start=0, page_length=10): - return self.get_events(remote_objectname, filters, page_length) + if not page_token: + if events.get("nextSyncToken"): + frappe.db.set_value("Google Calendar", account.name, "next_sync_token", events.get("nextSyncToken")) + frappe.db.commit() + break - def insert(self, doctype, doc): - if doctype == 'Events': - d = frappe.get_doc("Event", doc["name"]) - if has_permission(d, self.account.name): - try: - doctype = "Event" - e = self.insert_events(doctype, doc) - return e - except Exception: - frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error") + for idx, event in enumerate(list(results)): + frappe.publish_realtime('import_google_calendar', dict(progress=idx+1, total=len(list(results))), user=frappe.session.user) + frappe.get_doc({ + "doctype": "Event", + "description": event.get("description"), + "start_datetime": event.get("start_on"), + "end_datetime": event.get("ends_on"), + "all_day": event.get("all_day"), + "repeat_this_event": event.get("repeat_this_event"), + "repeat_on": event.get("repeat_on"), + "repeat_till": event.get("repeat_till"), + "sunday": event.get("sunday"), + "monday": event.get("monday"), + "tuesday": event.get("tuesday"), + "wednesday": event.get("wednesday"), + "thursday": event.get("thursday"), + "friday": event.get("friday"), + "saturday": event.get("saturday"), + "google_calendar_id": event.get("id") + }).insert() - def update(self, doctype, doc, migration_id): - if doctype == 'Events': - d = frappe.get_doc("Event", doc["name"]) - if has_permission(d, self.account.name): - if migration_id is not None: - try: - doctype = "Event" - return self.update_events(doctype, doc, migration_id) - except Exception: - frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error") +def insert_events(doc, method=None): + """ + Insert Events with Google Calendar + """ + google_calendar, account = get_credentials(doc.name) - def delete(self, doctype, migration_id): - if doctype == 'Events': - try: - return self.delete_events(migration_id) - except Exception: - frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error") + event = { + "summary": doc.summary, + "description": doc.description + } - def get_events(self, remote_objectname, filters, page_length): - page_token = None - results = [] - events = {"items": []} - while True: - try: - events = self.gcalendar.events().list(calendarId=self.account.gcalendar_id, maxResults=page_length, - singleEvents=False, showDeleted=True, syncToken=self.account.next_sync_token or None).execute() - except HttpError as err: - if err.resp.status in [410]: - events = self.gcalendar.events().list(calendarId=self.account.gcalendar_id, maxResults=page_length, - singleEvents=False, showDeleted=True, timeMin=add_years(None, -1).strftime('%Y-%m-%dT%H:%M:%SZ')).execute() - else: - frappe.log_error(err.resp, "GCalendar Events Fetch Error") - for event in events['items']: - event.update({'account': self.account.name}) - event.update({'calendar_tz': events['timeZone']}) - results.append(event) + dates = self.return_dates(doc) + event.update(dates) - page_token = events.get('nextPageToken') - if not page_token: - if events.get('nextSyncToken'): - frappe.db.set_value("GCalendar Account", self.connector.username, "next_sync_token", events.get('nextSyncToken')) - break - return list(results) + if migration_id: + event.update({"id": doc.name}) - def insert_events(self, doctype, doc, migration_id=None): + if doc.repeat_this_event != 0: + recurrence = self.return_recurrence(doctype, doc) + if recurrence: + event.update({"recurrence": ["RRULE:" + str(recurrence)]}) + + try: + remote_event = google_calendar.events().insert(calendarId=account.google_calendar_id, body=event).execute() + return {self.name_field: remote_event["id"]} + except Exception: + frappe.log_error(frappe.get_traceback(), _("Google Calendar Synchronization Error.")) + +def update_events(doc, method=None): + """ + Update Events with Google Calendar + """ + google_calendar, account = get_credentials(doc.name) + + try: + event = google_calendar.events().get(calendarId=account.google_calendar_id, eventId=doc.name).execute() event = { - 'summary': doc.summary, - 'description': doc.description + "summary": doc.summary, + "description": doc.description } + if doc.event_type == "Cancel": + event.update({"status": "cancelled"}) + dates = self.return_dates(doc) event.update(dates) - if migration_id: - event.update({"id": migration_id}) - if doc.repeat_this_event != 0: recurrence = self.return_recurrence(doctype, doc) - if not not recurrence: + if recurrence: event.update({"recurrence": ["RRULE:" + str(recurrence)]}) try: - remote_event = self.gcalendar.events().insert(calendarId=self.account.gcalendar_id, body=event).execute() - return {self.name_field: remote_event["id"]} - except Exception: - frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error") - - def update_events(self, doctype, doc, migration_id): - try: - event = self.gcalendar.events().get(calendarId=self.account.gcalendar_id, eventId=migration_id).execute() - event = { - 'summary': doc.summary, - 'description': doc.description - } - - if doc.event_type == "Cancel": - event.update({"status": "cancelled"}) - - dates = self.return_dates(doc) - event.update(dates) - - if doc.repeat_this_event != 0: - recurrence = self.return_recurrence(doctype, doc) - if recurrence: - event.update({"recurrence": ["RRULE:" + str(recurrence)]}) - - try: - updated_event = self.gcalendar.events().update(calendarId=self.account.gcalendar_id, eventId=migration_id, body=event).execute() - return {self.name_field: updated_event["id"]} - except Exception as e: - frappe.log_error(e, "GCalendar Synchronization Error") - except HttpError as err: - if err.resp.status in [404]: - self.insert_events(doctype, doc, migration_id) - else: - frappe.log_error(err.resp, "GCalendar Synchronization Error") - - def delete_events(self, migration_id): - try: - self.gcalendar.events().delete(calendarId=self.account.gcalendar_id, eventId=migration_id).execute() - except HttpError as err: - if err.resp.status in [410]: - pass - - def return_dates(self, doc): - timezone = frappe.db.get_value("System Settings", None, "time_zone") - if doc.end_datetime is None: - doc.end_datetime = doc.start_datetime - if doc.all_day == 1: - return { - 'start': { - 'date': doc.start_datetime.date().isoformat(), - 'timeZone': timezone, - }, - 'end': { - 'date': add_days(doc.end_datetime.date(), 1).isoformat(), - 'timeZone': timezone, - } - } + updated_event = google_calendar.events().update(calendarId=account.google_calendar_id, eventId=doc.name, body=event).execute() + return {self.name_field: updated_event["id"]} + except Exception as e: + frappe.log_error(e, "Google Calendar Synchronization Error.") + except HttpError as err: + if err.resp.status in [404]: + self.insert_events(doctype, doc) else: - return { - 'start': { - 'dateTime': doc.start_datetime.isoformat(), - 'timeZone': timezone, - }, - 'end': { - 'dateTime': doc.end_datetime.isoformat(), - 'timeZone': timezone, - } + frappe.log_error(err.resp, "Google Calendar Synchronization Error.") + +def delete_events(doc, method=None): + """ + Delete Events with Google Calendar + """ + google_calendar, account = get_credentials(doc.name) + + try: + google_calendar.events().delete(calendarId=account.google_calendar_id, eventId=doc.name).execute() + except HttpError as err: + if err.resp.status in [410]: + pass + +def return_dates(doc): + timezone = frappe.db.get_single_value("System Settings", "time_zone") + if not doc.end_datetime: + doc.end_datetime = doc.start_datetime + if doc.all_day == 1: + return { + "start": { + "date": doc.start_datetime.date().isoformat(), + "timeZone": timezone, + }, + "end": { + "date": add_days(doc.end_datetime.date(), 1).isoformat(), + "timeZone": timezone, } + } + else: + return { + "start": { + "dateTime": doc.start_datetime.isoformat(), + "timeZone": timezone, + }, + "end": { + "dateTime": doc.end_datetime.isoformat(), + "timeZone": timezone, + } + } - def return_recurrence(self, doctype, doc): - e = frappe.get_doc(doctype, doc.name) - if e.repeat_till is not None: - end_date = datetime.combine(e.repeat_till, datetime.min.time()).strftime('UNTIL=%Y%m%dT%H%M%SZ') - else: - end_date = None +def return_recurrence(doc): + e = frappe.get_doc("Event", doc.name) + if not e.repeat_till: + end_date = datetime.combine(e.repeat_till, datetime.min.time()).strftime("UNTIL=%Y%m%dT%H%M%SZ") + else: + end_date = None - day = [] - if e.repeat_on == "Every Day": - if e.monday == 1: - day.append("MO") - if e.tuesday == 1: - day.append("TU") - if e.wednesday == 1: - day.append("WE") - if e.thursday == 1: - day.append("TH") - if e.friday == 1: - day.append("FR") - if e.saturday == 1: - day.append("SA") - if e.sunday == 1: - day.append("SU") + day = [] + if e.repeat_on == "Every Day": + if e.monday == 1: + day.append("MO") + if e.tuesday == 1: + day.append("TU") + if e.wednesday == 1: + day.append("WE") + if e.thursday == 1: + day.append("TH") + if e.friday == 1: + day.append("FR") + if e.saturday == 1: + day.append("SA") + if e.sunday == 1: + day.append("SU") - day = "BYDAY=" + ",".join(str(d) for d in day) - frequency = "FREQ=WEEKLY" + day = "BYDAY=" + ",".join(str(d) for d in day) + frequency = "FREQ=WEEKLY" - elif e.repeat_on == "Every Week": - frequency = "FREQ=WEEKLY" - elif e.repeat_on == "Every Month": - frequency = "FREQ=MONTHLY;BYDAY=SU,MO,TU,WE,TH,FR,SA;BYSETPOS=-1" - end_date = datetime.combine(add_days(e.repeat_till, 1), datetime.min.time()).strftime('UNTIL=%Y%m%dT%H%M%SZ') - elif e.repeat_on == "Every Year": - frequency = "FREQ=YEARLY" - else: - return None + elif e.repeat_on == "Every Week": + frequency = "FREQ=WEEKLY" + elif e.repeat_on == "Every Month": + frequency = "FREQ=MONTHLY;BYDAY=SU,MO,TU,WE,TH,FR,SA;BYSETPOS=-1" + end_date = datetime.combine(add_days(e.repeat_till, 1), datetime.min.time()).strftime("UNTIL=%Y%m%dT%H%M%SZ") + elif e.repeat_on == "Every Year": + frequency = "FREQ=YEARLY" + else: + return None - wst = "WKST=SU" + wst = "WKST=SU" + elements = [frequency, end_date, wst, day] - elements = [frequency, end_date, wst, day] - - return ";".join(str(e) for e in elements if e is not None and not not e) + return ";".join(str(e) for e in elements if e is not None and not not e) From 44de671ac118c42ead9d483302f4643daf3d49af Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 24 Jul 2019 16:06:48 +0530 Subject: [PATCH 058/203] fix: urls for changelog --- frappe/change_log/v12/v12_0_0.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/frappe/change_log/v12/v12_0_0.md b/frappe/change_log/v12/v12_0_0.md index 897b351122..99b31c0b9a 100644 --- a/frappe/change_log/v12/v12_0_0.md +++ b/frappe/change_log/v12/v12_0_0.md @@ -1,8 +1,8 @@ # Version 12 Release Notes ### UI/UX Enhancements -1. [New Desktop](/docs/user/manual/en/using-erpnext/desktop) -1. [Keyboard Navigation](/docs/user/manual/en/using-erpnext/articles/keyboard-shortcuts) +1. [New Desktop](https://erpnext.com/docs/user/manual/en/using-erpnext/desktop) +1. [Keyboard Navigation](https://erpnext.com/docs/user/manual/en/using-erpnext/articles/keyboard-shortcuts) 1. [Link Preview](/version-12/release-notes/features#link-preview) 1. [New Upload Dialog](/version-12/release-notes/features#new-upload-dialog) 1. [Frequently visited links appear in Awesomebar results](/version-12/release-notes/features#frequently-visited-links-appear-in-awesomebar-results) @@ -10,20 +10,20 @@ 1. [List View Enhancements](/version-12/release-notes/features#list-view-enhancements) ### New Automation Module -1. [Assignment Rule](/docs/user/manual/en/setting-up/automation/assignment-rule) -1. [Milestones](/docs/user/manual/en/setting-up/automation/milestone-tracker) -1. [Auto Repeat](/docs/user/manual/en/setting-up/automation/auto-repeat) +1. [Assignment Rule](https://erpnext.com/docs/user/manual/en/setting-up/automation/assignment-rule) +1. [Milestones](https://erpnext.com/docs/user/manual/en/setting-up/automation/milestone-tracker) +1. [Auto Repeat](https://erpnext.com/docs/user/manual/en/setting-up/automation/auto-repeat) ### Other Changes & Enhancements -1. [Document Follow](/docs/user/manual/en/setting-up/email/document-follow) -1. [Energy Points](/docs/user/manual/en/setting-up/energy-point-system) -1. [Dashboards](/docs/user/manual/en/customize-erpnext/dashboard) +1. [Document Follow](https://erpnext.com/docs/user/manual/en/setting-up/email/document-follow) +1. [Energy Points](https://erpnext.com/docs/user/manual/en/setting-up/energy-point-system) +1. [Dashboards](https://erpnext.com/docs/user/manual/en/customize-erpnext/dashboard) 1. [Disable customization for single doctypes](/version-12/release-notes/features#disable-customization-for-single-doctypes) -1. [Email Linking](/docs/user/manual/en/setting-up/email/linking-emails-to-document) -1. [Google Contacts](/docs/user/manual/en/erpnext_integration/google_contacts) +1. [Email Linking](https://erpnext.com/docs/user/manual/en/setting-up/email/linking-emails-to-document) +1. [Google Contacts](https://erpnext.com/docs/user/manual/en/erpnext_integration/google_contacts) 1. [PDF Encryption](/version-12/release-notes/features#pdf-encryption) -1. [Raw Printing](/docs/user/manual/en/setting-up/print/raw-printing) +1. [Raw Printing](https://erpnext.com/docs/user/manual/en/setting-up/print/raw-printing) 1. [Web Form Refactor](/version-12/release-notes/features#web-form-refactor) -1. [Website Refactor](/docs/user/manual/en/website) -1. [Added Track Views field to Customize Form](/version-12/release-notes/features#added-track-views-field-to-customize-form) -1. [Add custom columns to any report](/version-12/release-notes/features#add-custom-columns-to-any-report) +1. [Website Refactor](https://erpnext.com/docs/user/manual/en/website) +1. [Added Track Views field to Customize Form](https://erpnext.com/version-12/release-notes/features#added-track-views-field-to-customize-form) +1. [Add custom columns to any report](https://erpnext.com/version-12/release-notes/features#add-custom-columns-to-any-report) From 5dc72f00f51d072a14ad816c8386ea705ddf2290 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 24 Jul 2019 16:09:56 +0530 Subject: [PATCH 059/203] fix: more url fixes --- frappe/change_log/v12/v12_0_0.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frappe/change_log/v12/v12_0_0.md b/frappe/change_log/v12/v12_0_0.md index 99b31c0b9a..215b612794 100644 --- a/frappe/change_log/v12/v12_0_0.md +++ b/frappe/change_log/v12/v12_0_0.md @@ -3,11 +3,11 @@ ### UI/UX Enhancements 1. [New Desktop](https://erpnext.com/docs/user/manual/en/using-erpnext/desktop) 1. [Keyboard Navigation](https://erpnext.com/docs/user/manual/en/using-erpnext/articles/keyboard-shortcuts) -1. [Link Preview](/version-12/release-notes/features#link-preview) -1. [New Upload Dialog](/version-12/release-notes/features#new-upload-dialog) -1. [Frequently visited links appear in Awesomebar results](/version-12/release-notes/features#frequently-visited-links-appear-in-awesomebar-results) -1. [Full Width Container]((/version-12/release-notes/features#full-width-container)) -1. [List View Enhancements](/version-12/release-notes/features#list-view-enhancements) +1. [Link Preview](https://erpnext.com/version-12/release-notes/features#link-preview) +1. [New Upload Dialog](https://erpnext.com/version-12/release-notes/features#new-upload-dialog) +1. [Frequently visited links appear in Awesomebar results](https://erpnext.com/version-12/release-notes/features#frequently-visited-links-appear-in-awesomebar-results) +1. [Full Width Container]((https://erpnext.com/version-12/release-notes/features#full-width-container)) +1. [List View Enhancements](https://erpnext.com/version-12/release-notes/features#list-view-enhancements) ### New Automation Module 1. [Assignment Rule](https://erpnext.com/docs/user/manual/en/setting-up/automation/assignment-rule) @@ -18,12 +18,12 @@ 1. [Document Follow](https://erpnext.com/docs/user/manual/en/setting-up/email/document-follow) 1. [Energy Points](https://erpnext.com/docs/user/manual/en/setting-up/energy-point-system) 1. [Dashboards](https://erpnext.com/docs/user/manual/en/customize-erpnext/dashboard) -1. [Disable customization for single doctypes](/version-12/release-notes/features#disable-customization-for-single-doctypes) +1. [Disable customization for single doctypes](https://erpnext.com/version-12/release-notes/features#disable-customization-for-single-doctypes) 1. [Email Linking](https://erpnext.com/docs/user/manual/en/setting-up/email/linking-emails-to-document) 1. [Google Contacts](https://erpnext.com/docs/user/manual/en/erpnext_integration/google_contacts) -1. [PDF Encryption](/version-12/release-notes/features#pdf-encryption) +1. [PDF Encryption](https://erpnext.com/version-12/release-notes/features#pdf-encryption) 1. [Raw Printing](https://erpnext.com/docs/user/manual/en/setting-up/print/raw-printing) -1. [Web Form Refactor](/version-12/release-notes/features#web-form-refactor) +1. [Web Form Refactor](https://erpnext.com/version-12/release-notes/features#web-form-refactor) 1. [Website Refactor](https://erpnext.com/docs/user/manual/en/website) 1. [Added Track Views field to Customize Form](https://erpnext.com/version-12/release-notes/features#added-track-views-field-to-customize-form) 1. [Add custom columns to any report](https://erpnext.com/version-12/release-notes/features#add-custom-columns-to-any-report) From 0405c1a23ba651c821a65c6677474802ca9c5f22 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 24 Jul 2019 16:23:28 +0530 Subject: [PATCH 060/203] fix: url fixes for changelog --- frappe/change_log/v12/v12_0_0.md | 44 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/frappe/change_log/v12/v12_0_0.md b/frappe/change_log/v12/v12_0_0.md index 897b351122..0e7db3d0f6 100644 --- a/frappe/change_log/v12/v12_0_0.md +++ b/frappe/change_log/v12/v12_0_0.md @@ -1,29 +1,29 @@ # Version 12 Release Notes ### UI/UX Enhancements -1. [New Desktop](/docs/user/manual/en/using-erpnext/desktop) -1. [Keyboard Navigation](/docs/user/manual/en/using-erpnext/articles/keyboard-shortcuts) -1. [Link Preview](/version-12/release-notes/features#link-preview) -1. [New Upload Dialog](/version-12/release-notes/features#new-upload-dialog) -1. [Frequently visited links appear in Awesomebar results](/version-12/release-notes/features#frequently-visited-links-appear-in-awesomebar-results) -1. [Full Width Container]((/version-12/release-notes/features#full-width-container)) -1. [List View Enhancements](/version-12/release-notes/features#list-view-enhancements) +1. [New Desktop](https://github.com/docs/user/manual/en/using-erpnext/desktop) +1. [Keyboard Navigation](https://github.com/docs/user/manual/en/using-erpnext/articles/keyboard-shortcuts) +1. [Link Preview](https://github.com/version-12/release-notes/features#link-preview) +1. [New Upload Dialog](https://github.com/version-12/release-notes/features#new-upload-dialog) +1. [Frequently visited links appear in Awesomebar results](https://github.com/version-12/release-notes/features#frequently-visited-links-appear-in-awesomebar-results) +1. [Full Width Container]((https://github.com/version-12/release-notes/features#full-width-container)) +1. [List View Enhancements](https://github.com/version-12/release-notes/features#list-view-enhancements) ### New Automation Module -1. [Assignment Rule](/docs/user/manual/en/setting-up/automation/assignment-rule) -1. [Milestones](/docs/user/manual/en/setting-up/automation/milestone-tracker) -1. [Auto Repeat](/docs/user/manual/en/setting-up/automation/auto-repeat) +1. [Assignment Rule](https://github.com/docs/user/manual/en/setting-up/automation/assignment-rule) +1. [Milestones](https://github.com/docs/user/manual/en/setting-up/automation/milestone-tracker) +1. [Auto Repeat](https://github.com/docs/user/manual/en/setting-up/automation/auto-repeat) ### Other Changes & Enhancements -1. [Document Follow](/docs/user/manual/en/setting-up/email/document-follow) -1. [Energy Points](/docs/user/manual/en/setting-up/energy-point-system) -1. [Dashboards](/docs/user/manual/en/customize-erpnext/dashboard) -1. [Disable customization for single doctypes](/version-12/release-notes/features#disable-customization-for-single-doctypes) -1. [Email Linking](/docs/user/manual/en/setting-up/email/linking-emails-to-document) -1. [Google Contacts](/docs/user/manual/en/erpnext_integration/google_contacts) -1. [PDF Encryption](/version-12/release-notes/features#pdf-encryption) -1. [Raw Printing](/docs/user/manual/en/setting-up/print/raw-printing) -1. [Web Form Refactor](/version-12/release-notes/features#web-form-refactor) -1. [Website Refactor](/docs/user/manual/en/website) -1. [Added Track Views field to Customize Form](/version-12/release-notes/features#added-track-views-field-to-customize-form) -1. [Add custom columns to any report](/version-12/release-notes/features#add-custom-columns-to-any-report) +1. [Document Follow](https://github.com/docs/user/manual/en/setting-up/email/document-follow) +1. [Energy Points](https://github.com/docs/user/manual/en/setting-up/energy-point-system) +1. [Dashboards](https://github.com/docs/user/manual/en/customize-erpnext/dashboard) +1. [Disable customization for single doctypes](https://github.com/version-12/release-notes/features#disable-customization-for-single-doctypes) +1. [Email Linking](https://github.com/docs/user/manual/en/setting-up/email/linking-emails-to-document) +1. [Google Contacts](https://github.com/docs/user/manual/en/erpnext_integration/google_contacts) +1. [PDF Encryption](https://github.com/version-12/release-notes/features#pdf-encryption) +1. [Raw Printing](https://github.com/docs/user/manual/en/setting-up/print/raw-printing) +1. [Web Form Refactor](https://github.com/version-12/release-notes/features#web-form-refactor) +1. [Website Refactor](https://github.com/docs/user/manual/en/website) +1. [Added Track Views field to Customize Form](https://github.com/version-12/release-notes/features#added-track-views-field-to-customize-form) +1. [Add custom columns to any report](https://github.com/version-12/release-notes/features#add-custom-columns-to-any-report) From b30199b7f5c9dc62563fc4c528432f1160de0fd3 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 24 Jul 2019 16:30:33 +0530 Subject: [PATCH 061/203] fix(security): Make jinja rendering tighter --- frappe/utils/jinja.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py index 7a27fb3c3b..f8745b82c3 100644 --- a/frappe/utils/jinja.py +++ b/frappe/utils/jinja.py @@ -71,7 +71,7 @@ def render_template(template, context, is_path=None, safe_render=True): or (template.endswith('.html') and '\n' not in template)): return get_jenv().get_template(template).render(context) else: - if safe_render and ".__" in template: + if safe_render and "__" in template: throw("Illegal template") try: return get_jenv().from_string(template).render(context) From 00588d5597f7782955866ff1ad06913bce1777d9 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 24 Jul 2019 20:45:19 +0530 Subject: [PATCH 062/203] fix: rename google calender object --- .../integrations/doctype/google_calendar/google_calendar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index c3aa7254d3..181083548f 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -173,11 +173,11 @@ def get_events(doc, method=None, page_length=10): while True: try: - events = self.google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, + events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, singleEvents=False, showDeleted=True, syncToken=account.next_sync_token or None).execute() except HttpError as err: if err.resp.status in [410]: - events = self.google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, + events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, singleEvents=False, showDeleted=True, timeMin=add_years(None, -1).strftime("%Y-%m-%dT%H:%M:%SZ")).execute() else: frappe.log_error(err.resp, "Google Calendar Events Fetch Error.") From 103e89406412bd8ce8ad4d433817ce08e8c490f7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 25 Jul 2019 11:37:31 +0530 Subject: [PATCH 063/203] fix: more button visibility bug --- .../js/frappe/form/multi_select_dialog.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index 0d8d2caca1..e5d4d91d32 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -201,11 +201,12 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ let $row = $(`
- +
${contents}
`); + head ? $row.addClass('list-item--head') : $row = $(`
`).append($row); return $row; @@ -219,14 +220,10 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ if (!frappe.flags.auto_scroll) { this.empty_list(); } + more_btn.hide(); - if(results.length === 0) { - this.empty_list(); - more_btn.hide(); - return; - } else if(more) { - more_btn.show(); - } + if (results.length === 0) return; + if (more) more_btn.show(); results.forEach((result) => { me.$results.append(me.make_list_row(result)); @@ -303,10 +300,6 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ return a.parsed_date - b.parsed_date; }); - // Preselect oldest entry - if (me.start < 1) { - results[0].checked = 1; - } } me.render_result_list(results, more); } From c42a25bdd51a80acf408b49e20ae20cdb686dd50 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 25 Jul 2019 13:40:28 +0530 Subject: [PATCH 064/203] feat: docstring for api response --- .../google_calendar/google_calendar.json | 39 ++- .../google_calendar/google_calendar.py | 312 +++++++++++++++--- 2 files changed, 288 insertions(+), 63 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.json b/frappe/integrations/doctype/google_calendar/google_calendar.json index e001317956..cecf8138cb 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.json +++ b/frappe/integrations/doctype/google_calendar/google_calendar.json @@ -1,5 +1,5 @@ { - "autoname": "field:user", + "autoname": "format:Calendar-{user}", "creation": "2019-07-06 17:54:09.450100", "doctype": "DocType", "editable_grid": 1, @@ -11,8 +11,11 @@ "authorize_google_calendar_access", "cb_00", "calendar_name", + "sb_01", + "pull_from_google_calendar", + "cb_01", + "push_to_google_calendar", "section_break_3", - "section_break_4", "refresh_token", "authorization_code", "column_break_6", @@ -38,8 +41,8 @@ "in_list_view": 1, "label": "User", "options": "User", - "reqd": 1, - "unique": 1 + "read_only": 1, + "reqd": 1 }, { "description": "The name that will appear in Google Calendar", @@ -49,14 +52,9 @@ "reqd": 1 }, { - "depends_on": "eval:doc.enabled", "fieldname": "section_break_3", "fieldtype": "Section Break" }, - { - "fieldname": "section_break_4", - "fieldtype": "Section Break" - }, { "fieldname": "refresh_token", "fieldtype": "Data", @@ -92,9 +90,30 @@ "fieldname": "authorize_google_calendar_access", "fieldtype": "Button", "label": "Authorize Google Calendar Access" + }, + { + "fieldname": "sb_01", + "fieldtype": "Section Break", + "label": "Sync" + }, + { + "default": "1", + "fieldname": "pull_from_google_calendar", + "fieldtype": "Check", + "label": "Pull from Google Calendar" + }, + { + "fieldname": "cb_01", + "fieldtype": "Column Break" + }, + { + "default": "1", + "fieldname": "push_to_google_calendar", + "fieldtype": "Check", + "label": "Push to Google Calendar" } ], - "modified": "2019-07-24 15:02:00.233602", + "modified": "2019-07-25 11:21:58.509798", "modified_by": "Administrator", "module": "Integrations", "name": "Google Calendar", diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 181083548f..a0bab99826 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -12,20 +12,31 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import get_request_site_address from googleapiclient.errors import HttpError +from frappe.utils import add_days, add_years SCOPES = "https://www.googleapis.com/auth/calendar/v3" class GoogleCalendar(Document): - def validate(self): - if not frappe.db.get_single_value("Google Settings", "enable"): + def validate_google_settings(self): + google_settings = frappe.get_single("Google Settings") + if not google_settings.enable: frappe.throw(_("Enable Google API in Google Settings.")) - def get_access_token(self): - google_settings = frappe.get_doc("Google Settings") + if not google_settings.client_id or not google_settings.client_secret: + frappe.throw(_("Enter Client Id and Client Secret in Google Settings.")) - if not google_settings.enable: - frappe.throw(_("Google Calendar Integration is disabled.")) + return google_settings + + def validate(self): + self.validate_google_settings() + + if frappe.db.exists("Google Calendar", {"user": self.user, "calendar_name": self.calendar_name}) and \ + not frappe.db.get_value("Google Calendar", {"user": self.user, "calendar_name": self.calendar_name}, "name") == self.name: + frappe.throw(_("Google Calendar already exists for user {0} and name {1}").format(self.user, self.calendar_name)) + + def get_access_token(self): + google_settings = self.validate_google_settings() if not self.refresh_token: button_label = frappe.bold(_("Allow Google Calendar Access")) @@ -130,96 +141,152 @@ def get_credentials(g_calendar): check_remote_calendar(account, google_calendar) + account.load_from_db() return google_calendar, account def check_remote_calendar(account, google_calendar): - def _create_calendar(): + def _create_calendar(account): calendar = { "summary": account.calendar_name, - "timeZone": frappe.db.get_value("System Settings", None, "time_zone") + "timeZone": frappe.db.get_single_value("System Settings", "time_zone") } try: created_calendar = google_calendar.calendars().insert(body=calendar).execute() - frappe.db.set_value("Google Calendar", account.name, "google_calendar_id", created_calendar["id"]) + frappe.db.set_value("Google Calendar", account.name, "google_calendar_id", created_calendar.get("id")) frappe.db.commit() except Exception: frappe.log_error(frappe.get_traceback()) try: - if not account.google_calendar_id: + if account.google_calendar_id: try: + account.load_from_db() google_calendar.calendars().get(calendarId=account.google_calendar_id).execute() except Exception: frappe.log_error(frappe.get_traceback()) else: - _create_calendar() + _create_calendar(account) except HttpError as err: - if err.resp.status in [403, 500, 503]: + if err.resp.status in [403, 404, 500, 503]: time.sleep(5) - elif err.resp.status in [404]: - _create_calendar() - else: raise + _create_calendar(account) + else: + frappe.log_error(frappe.get_traceback(), _("Google Calendar Synchronization Error.")) + def get_events(doc, method=None, page_length=10): """ Sync Events with Google Calendar """ + if not doc.pull_from_google_calendar: + return + google_calendar, account = get_credentials(doc.name) page_token = None results = [] - events = { - "items": [] - } + print("1") while True: try: + """ + API Response + { + 'kind': 'calendar#events', + 'etag': '"etag"', + 'summary': 'Test Calendar', + 'updated': '2019-07-24T17:46:24.366Z', + 'timeZone': 'Asia/Kolkata', + 'accessRole': 'owner', + 'defaultReminders': [], + 'nextSyncToken': 'nextSyncToken', + 'items': [ + { + 'kind': 'calendar#event', + 'etag': '"etag"', + 'id': 'id', + 'status': 'confirmed', + 'htmlLink': 'https://www.google.com/calendar/event?eid=eid', + 'created': '2019-07-24T17:46:24.000Z', + 'updated': '2019-07-24T17:46:24.366Z', + 'summary': 'qusk', + 'creator': { + 'email': 'himanshu@iwebnotes.com' + }, + 'organizer': { + 'email': 'calendar_id', + 'displayName': 'Test Calendar', + 'self': True + }, + 'start': { + 'dateTime': '2019-07-26T20:00:00+05:30' + }, + 'end': { + 'dateTime': '2019-07-26T21:00:00+05:30' + }, + 'iCalUID': 'UID', + 'sequence': 0, + 'recurrence': ['RRULE:FREQ=DAILY'], + 'reminders': { + 'useDefault': True + } + } + ] + } + """ events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, singleEvents=False, showDeleted=True, syncToken=account.next_sync_token or None).execute() + print("2") + print(events) except HttpError as err: - if err.resp.status in [410]: + if err.resp.status in [404, 410]: events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, singleEvents=False, showDeleted=True, timeMin=add_years(None, -1).strftime("%Y-%m-%dT%H:%M:%SZ")).execute() + print("3") + print(events) else: frappe.log_error(err.resp, "Google Calendar Events Fetch Error.") - for event in events["items"]: - event.update({"account": account.name}) - event.update({"calendar_tz": events["timeZone"]}) + for event in events.get("items"): results.append(event) - page_token = events.get("nextPageToken") - if not page_token: + if not events.get("nextPageToken"): if events.get("nextSyncToken"): frappe.db.set_value("Google Calendar", account.name, "next_sync_token", events.get("nextSyncToken")) frappe.db.commit() break - for idx, event in enumerate(list(results)): + print(results) + for idx, event in enumerate(results): frappe.publish_realtime('import_google_calendar', dict(progress=idx+1, total=len(list(results))), user=frappe.session.user) - - frappe.get_doc({ - "doctype": "Event", - "description": event.get("description"), - "start_datetime": event.get("start_on"), - "end_datetime": event.get("ends_on"), - "all_day": event.get("all_day"), - "repeat_this_event": event.get("repeat_this_event"), - "repeat_on": event.get("repeat_on"), - "repeat_till": event.get("repeat_till"), - "sunday": event.get("sunday"), - "monday": event.get("monday"), - "tuesday": event.get("tuesday"), - "wednesday": event.get("wednesday"), - "thursday": event.get("thursday"), - "friday": event.get("friday"), - "saturday": event.get("saturday"), - "google_calendar_id": event.get("id") - }).insert() + return_recurrence(event.get("recurrence")) + # if not frappe.db.exists("Event", {"subject": event.get("summary")}): + # frappe.get_doc({ + # "doctype": "Event", + # "subject": event.get("summary"), + # "description": event.get("description"), + # "start_on": event.get("start").get("dateTime"), + # "ends_on": event.get("end").get("dateTime"), + # "all_day": event.get("all_day"), + # "repeat_this_event": event.get("repeat_this_event"), + # "repeat_on": event.get("repeat_on"), + # "repeat_till": event.get("repeat_till"), + # "sunday": event.get("sunday"), + # "monday": event.get("monday"), + # "tuesday": event.get("tuesday"), + # "wednesday": event.get("wednesday"), + # "thursday": event.get("thursday"), + # "friday": event.get("friday"), + # "saturday": event.get("saturday"), + # "google_calendar_id": event.get("id") + # }).insert(ignore_permissions=True) def insert_events(doc, method=None): """ Insert Events with Google Calendar """ + if not doc.push_to_google_calendar: + return + google_calendar, account = get_credentials(doc.name) event = { @@ -227,20 +294,19 @@ def insert_events(doc, method=None): "description": doc.description } - dates = self.return_dates(doc) + dates = return_dates(doc) event.update(dates) if migration_id: event.update({"id": doc.name}) if doc.repeat_this_event != 0: - recurrence = self.return_recurrence(doctype, doc) + recurrence = return_recurrence(doc) if recurrence: event.update({"recurrence": ["RRULE:" + str(recurrence)]}) try: remote_event = google_calendar.events().insert(calendarId=account.google_calendar_id, body=event).execute() - return {self.name_field: remote_event["id"]} except Exception: frappe.log_error(frappe.get_traceback(), _("Google Calendar Synchronization Error.")) @@ -260,22 +326,21 @@ def update_events(doc, method=None): if doc.event_type == "Cancel": event.update({"status": "cancelled"}) - dates = self.return_dates(doc) + dates = return_dates(doc) event.update(dates) if doc.repeat_this_event != 0: - recurrence = self.return_recurrence(doctype, doc) + recurrence = return_recurrence(doc) if recurrence: event.update({"recurrence": ["RRULE:" + str(recurrence)]}) try: updated_event = google_calendar.events().update(calendarId=account.google_calendar_id, eventId=doc.name, body=event).execute() - return {self.name_field: updated_event["id"]} except Exception as e: frappe.log_error(e, "Google Calendar Synchronization Error.") except HttpError as err: if err.resp.status in [404]: - self.insert_events(doctype, doc) + pass else: frappe.log_error(err.resp, "Google Calendar Synchronization Error.") @@ -318,8 +383,7 @@ def return_dates(doc): } } -def return_recurrence(doc): - e = frappe.get_doc("Event", doc.name) +def return_recurrence_for_google_calendar(recurrence): if not e.repeat_till: end_date = datetime.combine(e.repeat_till, datetime.min.time()).strftime("UNTIL=%Y%m%dT%H%M%SZ") else: @@ -359,3 +423,145 @@ def return_recurrence(doc): elements = [frequency, end_date, wst, day] return ";".join(str(e) for e in elements if e is not None and not not e) + +def parse_recurrence(recureence): + pass + + +""" + - Recurrence Daily + { + 'kind': 'calendar#events', + 'etag': '"p32k8vl58mj7u60g"', + 'summary': 'Test Calendar', + 'updated': '2019-07-25T06:09:34.681Z', + 'timeZone': 'Asia/Kolkata', + 'accessRole': 'owner', + 'defaultReminders': [], + 'nextSyncToken': 'CKiP1Ki0z-MCEKiP1Ki0z-MCGAU=', + 'items': [ + { + 'kind': 'calendar#event', + 'etag': '"3128069949362000"', + 'id': '6okmku7u8o4itknb7l1alu94ns', + 'status': 'confirmed', + 'htmlLink': 'https://www.google.com/calendar/event?eid=Nm9rbWt1N3U4bzRpdGtuYjdsMWFsdTk0bnNfMjAxOTA3MjdUMDYzMDAwWiBpd2Vibm90ZXMuY29tX2hqODFkZDA4aHJwaWNsbWY0anI3OWJzNG44QGc', + 'created': '2019-07-25T06:08:21.000Z', + 'updated': '2019-07-25T06:09:34.681Z', + 'summary': 'asdf', + 'creator': { + 'email': 'himanshu@iwebnotes.com' + }, + 'organizer': { + 'email': 'iwebnotes.com_hj81dd08hrpiclmf4jr79bs4n8@group.calendar.google.com', + 'displayName': 'Test Calendar', + 'self': True + }, + 'start': { + 'dateTime': '2019-07-27T12:00:00+05:30', + 'timeZone': 'Asia/Kolkata' + }, + 'end': { + 'dateTime': '2019-07-27T13:00:00+05:30', + 'timeZone': 'Asia/Kolkata' + }, + 'recurrence': ['RRULE:FREQ=DAILY'], + 'iCalUID': '6okmku7u8o4itknb7l1alu94ns@google.com', + 'sequence': 1, + 'reminders': { + 'useDefault': True + } + } + ] + } + - Recurrence Weekly + { + 'kind': 'calendar#events', + 'etag': '"p320afgm8nv7u60g"', + 'summary': 'Test Calendar', + 'updated': '2019-07-25T06:59:54.288Z', + 'timeZone': 'Asia/Kolkata', + 'accessRole': 'owner', + 'defaultReminders': [], + 'nextSyncToken': 'CICnwsi_z-MCEICnwsi_z-MCGAU=', + 'items': [ + { + 'kind': 'calendar#event', + 'etag': '"3128075988576000"', + 'id': '33n5br0htu51suqih4k8990181', + 'status': 'confirmed', + 'htmlLink': 'https://www.google.com/calendar/event?eid=MzNuNWJyMGh0dTUxc3VxaWg0azg5OTAxODFfMjAxOTA3MjVUMTIzMDAwWiBpd2Vibm90ZXMuY29tX25mOHAyaTFnazU5NDI2dTBsNzA4amU0OWVjQGc', + 'created': '2019-07-25T06:59:47.000Z', + 'updated': '2019-07-25T06:59:54.288Z', + 'summary': 'Event', + 'creator': { + 'email': 'himanshu@iwebnotes.com' + }, + 'organizer': { + 'email': 'iwebnotes.com_nf8p2i1gk59426u0l708je49ec@group.calendar.google.com', + 'displayName': 'Test Calendar', + 'self': True + }, + 'start': { + 'dateTime': '2019-07-25T18:00:00+05:30', + 'timeZone': 'Asia/Kolkata' + }, + 'end': { + 'dateTime': '2019-07-25T19:00:00+05:30', + 'timeZone': 'Asia/Kolkata' + }, + 'recurrence': ['RRULE:FREQ=WEEKLY;BYDAY=TH'], + 'iCalUID': '33n5br0htu51suqih4k8990181@google.com', + 'sequence': 1, + 'reminders': { + 'useDefault': True + } + } + ] + } + - Recurrence Monthly on a Day + { + 'kind': 'calendar#events', + 'etag': '"p32oajev9ob7u60g"', + 'summary': 'Test Calendar', + 'updated': '2019-07-25T07:14:28.686Z', + 'timeZone': 'Asia/Kolkata', + 'accessRole': 'owner', + 'defaultReminders': [], + 'nextSyncToken': 'CLCpu-nCz-MCELCpu-nCz-MCGAU=', + 'items': [ + { + 'kind': 'calendar#event', + 'etag': '"3128077737372000"', + 'id': '4up16101ptr37i5594asp0e692', + 'status': 'confirmed', + 'htmlLink': 'https://www.google.com/calendar/event?eid=NHVwMTYxMDFwdHIzN2k1NTk0YXNwMGU2OTJfMjAxOTA3MjVUMTMzMDAwWiBpd2Vibm90ZXMuY29tX25mOHAyaTFnazU5NDI2dTBsNzA4amU0OWVjQGc', + 'created': '2019-07-25T07:14:08.000Z', + 'updated': '2019-07-25T07:14:28.686Z', + 'summary': 'monthly 4 thusday', + 'creator': { + 'email': 'himanshu@iwebnotes.com' + }, + 'organizer': { + 'email': 'iwebnotes.com_nf8p2i1gk59426u0l708je49ec@group.calendar.google.com', + 'displayName': 'Test Calendar', + 'self': True + }, + 'start': { + 'dateTime': '2019-07-25T19:00:00+05:30', + 'timeZone': 'Asia/Kolkata' + }, + 'end': { + 'dateTime': '2019-07-25T20:00:00+05:30', + 'timeZone': 'Asia/Kolkata' + }, + 'recurrence': ['RRULE:FREQ=MONTHLY;BYDAY=4TH'], + 'iCalUID': '4up16101ptr37i5594asp0e692@google.com', + 'sequence': 1, + 'reminders': { + 'useDefault': True + } + } + ] + } +""" \ No newline at end of file From 0d2043d76bbfa97f69c5a35467d645a1c0f74657 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 25 Jul 2019 17:03:35 +0530 Subject: [PATCH 065/203] refactor: translated table headings --- frappe/public/js/frappe/web_form/web_form_list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/web_form/web_form_list.js b/frappe/public/js/frappe/web_form/web_form_list.js index 709ee66e06..c92b62f013 100644 --- a/frappe/public/js/frappe/web_form/web_form_list.js +++ b/frappe/public/js/frappe/web_form/web_form_list.js @@ -154,7 +154,7 @@ export default class WebFormList { add_heading(row, "Sr."); this.columns.forEach(col => { let th = document.createElement("th"); - let text = document.createTextNode(col.label); + let text = document.createTextNode(__(col.label)); th.appendChild(text); row.appendChild(th); }); From fb8993663c2e587d78667937ad356d17610a2214 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 25 Jul 2019 20:49:00 +0530 Subject: [PATCH 066/203] fix(security): Disallow unnecessary characters in group_by and fields --- frappe/model/db_query.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 53a1c6c13d..7767d2a021 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -240,6 +240,9 @@ class DatabaseQuery(object): _is_query(field) + invalid_characters_regex = r".*[^a-zA-Z0-9-_ ,`'\"\*\.\(\)].*" + if re.match(invalid_characters_regex, field): + frappe.throw(_("Illegal characters in SQL query")) def extract_tables(self): """extract tables from fields""" @@ -688,6 +691,9 @@ class DatabaseQuery(object): if 'select' in _lower and ' from ' in _lower: frappe.throw(_('Cannot use sub-query in order by')) + invalid_characters_regex = r".*[^a-z0-9-_ ,`'\"\.\(\)].*" + if re.match(invalid_characters_regex, _lower): + frappe.throw(_("Illegal characters in SQL query")) for field in parameters.split(","): if "." in field and field.strip().startswith("`tab"): From af35e82b1b6b13a756f0a6e00855321d8cb397c7 Mon Sep 17 00:00:00 2001 From: lapphan <11986206+lapphan@users.noreply.github.com> Date: Thu, 25 Jul 2019 23:10:24 +0700 Subject: [PATCH 067/203] fix: Check wrong OpenID's spec email claims --- frappe/utils/oauth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/oauth.py b/frappe/utils/oauth.py index f5cf5d81e8..362953336b 100644 --- a/frappe/utils/oauth.py +++ b/frappe/utils/oauth.py @@ -142,7 +142,7 @@ def get_info_via_oauth(provider, code, decoder=None, id_token=False): api_endpoint_args = oauth2_providers[provider].get("api_endpoint_args") info = session.get(api_endpoint, params=api_endpoint_args).json() - if not (info.get("verified_email") or info.get("verified")): + if not (info.get("email_verified") or info.get("email")): frappe.throw(_("Email not verified with {0}").format(provider.title())) return info From 8d1350135eb807673b5f61361ca3423ab415c407 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 25 Jul 2019 23:10:43 +0530 Subject: [PATCH 068/203] fix: tests --- frappe/public/js/frappe/list/base_list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index b81d7695bd..79555b7e48 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -598,7 +598,7 @@ class FilterArea { } const doctype_fields = this.list_view.meta.fields; - + let session_defaults = frappe.db.get_single_value('Session Default Settings', 'session_defaults'); fields = fields.concat(doctype_fields.filter( df => df.in_standard_filter && frappe.model.is_value_type(df.fieldtype) @@ -617,7 +617,7 @@ class FilterArea { options = options.join("\n"); } } - let default_value = fieldtype === 'Link' ? frappe.defaults.get_user_default(options) : null; + let default_value = ((fieldtype === 'Link') && (session_defaults.ref_doctype === options)) ? frappe.defaults.get_user_default(options) : null; return { fieldtype: fieldtype, label: __(df.label), From be13e1b669532bb5bd9baf0c89c78836e671175d Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 26 Jul 2019 00:21:05 +0530 Subject: [PATCH 069/203] feat: make events great again --- frappe/desk/doctype/event/event.json | 1143 ++++++-------------------- frappe/desk/doctype/event/event.py | 55 +- 2 files changed, 270 insertions(+), 928 deletions(-) diff --git a/frappe/desk/doctype/event/event.json b/frappe/desk/doctype/event/event.json index d20d678a0e..24237be32a 100644 --- a/frappe/desk/doctype/event/event.json +++ b/frappe/desk/doctype/event/event.json @@ -1,944 +1,277 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "EV.#####", - "beta": 0, - "creation": "2013-06-10 13:17:47", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, - "engine": "InnoDB", + "allow_import": 1, + "autoname": "EV.#####", + "creation": "2013-06-10 13:17:47", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "details", + "subject", + "event_category", + "event_type", + "send_reminder", + "repeat_this_event", + "column_break_4", + "starts_on", + "ends_on", + "all_day", + "section_break_13", + "repeat_on", + "repeat_till", + "column_break_16", + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday", + "section_break_8", + "color", + "section_break_6", + "description", + "participants", + "event_participants", + "sb_00", + "google_calendar_name", + "section_break_30", + "google_calendar_event" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "details", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "details", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "subject", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Subject", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "subject", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Subject", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "event_category", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Event Category", - "length": 0, - "no_copy": 0, - "options": "Event\nMeeting\nCall\nSent/Received Email\nOther", - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "event_category", + "fieldtype": "Select", + "label": "Event Category", + "options": "Event\nMeeting\nCall\nSent/Received Email\nOther" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "event_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Event Type", - "length": 0, - "no_copy": 0, - "oldfieldname": "event_type", - "oldfieldtype": "Select", - "options": "Private\nPublic\nCancelled", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "event_type", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Event Type", + "oldfieldname": "event_type", + "oldfieldtype": "Select", + "options": "Private\nPublic\nCancelled", + "reqd": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fieldname": "send_reminder", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Send an email reminder in the morning", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "1", + "fieldname": "send_reminder", + "fieldtype": "Check", + "label": "Send an email reminder in the morning" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "repeat_this_event", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Repeat this Event", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "repeat_this_event", + "fieldtype": "Check", + "label": "Repeat this Event" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "starts_on", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Starts on", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "starts_on", + "fieldtype": "Datetime", + "label": "Starts on", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "ends_on", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Ends on", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "ends_on", + "fieldtype": "Datetime", + "label": "Ends on" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "all_day", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "All Day", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "all_day", + "fieldtype": "Check", + "label": "All Day" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "repeat_this_event", - "fieldname": "section_break_13", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "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, - "translatable": 0, - "unique": 0 - }, + "depends_on": "repeat_this_event", + "fieldname": "section_break_13", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "repeat_this_event", - "fieldname": "repeat_on", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Repeat On", - "length": 0, - "no_copy": 0, - "options": "\nEvery Day\nEvery Week\nEvery Month\nEvery Year", - "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, - "translatable": 0, - "unique": 0 - }, + "depends_on": "repeat_this_event", + "fieldname": "repeat_on", + "fieldtype": "Select", + "in_global_search": 1, + "label": "Repeat On", + "options": "\nDaily\nWeekly\nMonthly\nYearly" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "repeat_this_event", - "description": "Leave blank to repeat always", - "fieldname": "repeat_till", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Repeat Till", - "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, - "translatable": 0, - "unique": 0 - }, + "depends_on": "repeat_this_event", + "description": "Leave blank to repeat always", + "fieldname": "repeat_till", + "fieldtype": "Date", + "label": "Repeat Till" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_16", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_16", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", - "fieldname": "monday", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Monday", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Weekly\"", + "fieldname": "monday", + "fieldtype": "Check", + "label": "Monday" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", - "fieldname": "tuesday", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Tuesday", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Weekly\"", + "fieldname": "tuesday", + "fieldtype": "Check", + "label": "Tuesday" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", - "fieldname": "wednesday", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Wednesday", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Weekly\"", + "fieldname": "wednesday", + "fieldtype": "Check", + "label": "Wednesday" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", - "fieldname": "thursday", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Thursday", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Weekly\"", + "fieldname": "thursday", + "fieldtype": "Check", + "label": "Thursday" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", - "fieldname": "friday", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Friday", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Weekly\"", + "fieldname": "friday", + "fieldtype": "Check", + "label": "Friday" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", - "fieldname": "saturday", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Saturday", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Weekly\"", + "fieldname": "saturday", + "fieldtype": "Check", + "label": "Saturday" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", - "fieldname": "sunday", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sunday", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Weekly\"", + "fieldname": "sunday", + "fieldtype": "Check", + "label": "Sunday" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_8", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_8", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "color", - "fieldtype": "Color", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Color", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "color", + "fieldtype": "Color", + "label": "Color" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "oldfieldname": "description", - "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "300px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "description", + "fieldtype": "Text Editor", + "in_global_search": 1, + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "print_width": "300px", "width": "300px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "participants", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Participants", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "participants", + "fieldtype": "Section Break", + "label": "Participants", + "oldfieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "event_participants", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Event Participants", - "length": 0, - "no_copy": 0, - "options": "Event Participants", - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 + "fieldname": "event_participants", + "fieldtype": "Table", + "label": "Event Participants", + "options": "Event Participants" + }, + { + "collapsible": 1, + "depends_on": "eval:doc.google_calendar_name && doc.google_calendar_event", + "fieldname": "sb_00", + "fieldtype": "Section Break", + "label": "Google Calendar" + }, + { + "fieldname": "google_calendar_name", + "fieldtype": "Data", + "label": "Google Calendar Name", + "read_only": 1 + }, + { + "fieldname": "section_break_30", + "fieldtype": "Column Break" + }, + { + "fieldname": "google_calendar_event", + "fieldtype": "Data", + "label": "Google Calendar Event", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-calendar", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-09-25 12:40:33.958600", - "modified_by": "Administrator", - "module": "Desk", - "name": "Event", - "owner": "Administrator", + ], + "icon": "fa fa-calendar", + "idx": 1, + "modified": "2019-07-26 00:01:18.005707", + "modified_by": "Administrator", + "module": "Desk", + "name": "Event", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "All", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 1, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 1, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_order": "DESC", - "title_field": "subject", - "track_changes": 1, - "track_seen": 1, + ], + "read_only": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "subject", + "track_changes": 1, + "track_seen": 1, "track_views": 1 } \ No newline at end of file diff --git a/frappe/desk/doctype/event/event.py b/frappe/desk/doctype/event/event.py index e88c11b4f8..b9bab0a204 100644 --- a/frappe/desk/doctype/event/event.py +++ b/frappe/desk/doctype/event/event.py @@ -9,6 +9,7 @@ import json from frappe.utils import (getdate, cint, add_months, date_diff, add_days, nowdate, get_datetime_str, cstr, get_datetime, now_datetime, format_datetime) +from frappe import _ from frappe.model.document import Document from frappe.utils.user import get_enabled_system_users from frappe.desk.reportview import get_filters_cond @@ -18,18 +19,15 @@ communication_mapping = {"": "Event", "Event": "Event", "Meeting": "Meeting", "C class Event(Document): def validate(self): - if not self.starts_on: - self.starts_on = now_datetime() - if self.starts_on and self.ends_on and get_datetime(self.starts_on) > get_datetime(self.ends_on): - frappe.msgprint(frappe._("Event end must be after start"), raise_exception=True) + frappe.throw(_("Event's End On cannot be before Start On.")) if self.starts_on == self.ends_on: # this scenario doesn't make sense i.e. it starts and ends at the same second! self.ends_on = None - if getdate(self.starts_on) != getdate(self.ends_on) and self.repeat_on == "Every Day": - frappe.msgprint(frappe._("Every day events should finish on the same day."), raise_exception=True) + if getdate(self.starts_on) != getdate(self.ends_on) and self.repeat_on == "Daily": + frappe.throw(_("Daily Events should finish on the Same Day.")) def on_update(self): self.sync_communication() @@ -38,17 +36,18 @@ class Event(Document): communications = frappe.get_all("Communication", dict(reference_doctype=self.doctype, reference_name=self.name)) if communications: for communication in communications: - frappe.get_doc("Communication", communication.name).delete() + frappe.delete_doc_if_exists("Communication", communication.name) def sync_communication(self): if self.event_participants: for participant in self.event_participants: - comms = frappe.get_list("Communication", filters=[ + filters = [ ["Communication", "reference_doctype", "=", self.doctype], ["Communication", "reference_name", "=", self.name], ["Communication Link", "link_doctype", "=", participant.reference_doctype], ["Communication Link", "link_name", "=", participant.reference_docname] - ], fields=["name"]) + ] + comms = frappe.get_list("Communication", filters=filters, fields=["name"]) if comms: for comm in comms: @@ -82,12 +81,14 @@ def delete_communication(event, reference_doctype, reference_docname): if isinstance(event, string_types): event = json.loads(event) - comms = frappe.get_list("Communication", filters=[ + filters = [ ["Communication", "reference_doctype", "=", event.get("doctype")], ["Communication", "reference_name", "=", event.get("name")], ["Communication Link", "link_doctype", "=", deleted_participant.reference_doctype], ["Communication Link", "link_name", "=", deleted_participant.reference_docname] - ], fields=["name"]) + ] + + comms = frappe.get_list("Communication", filters=filters, fields=["name"]) if comms: deletion = [] @@ -139,8 +140,10 @@ def send_event_digest(): def get_events(start, end, user=None, for_reminder=False, filters=None): if not user: user = frappe.session.user + if isinstance(filters, string_types): filters = json.loads(filters) + events = frappe.db.sql("""select `name`, subject, description, color, starts_on, ends_on, owner, all_day, event_type, repeat_this_event, repeat_on,repeat_till, monday, tuesday, wednesday, thursday, friday, saturday, sunday @@ -190,24 +193,27 @@ def get_events(start, end, user=None, for_reminder=False, filters=None): e.ends_on = get_datetime_str(e.ends_on) event_start, time_str = get_datetime_str(e.starts_on).split(" ") - if cstr(e.repeat_till) == "": - repeat = "3000-01-01" - else: - repeat = e.repeat_till - if e.repeat_on=="Every Year": + + repeat = "3000-01-01" if cstr(e.repeat_till) == "" else e.repeat_till + + if e.repeat_on == "Yearly": start_year = cint(start.split("-")[0]) end_year = cint(end.split("-")[0]) + + # creates a string with date (27) and month (07) eg: 07-27 event_start = "-".join(event_start.split("-")[1:]) # repeat for all years in period for year in range(start_year, end_year+1): date = str(year) + "-" + event_start if getdate(date) >= getdate(start) and getdate(date) <= getdate(end) and getdate(date) <= getdate(repeat): + add_event(e, date) remove_events.append(e) - if e.repeat_on=="Every Month": + if e.repeat_on=="Monthly": + # creates a string with date (27) and month (07) and year (2019) eg: 2019-07-27 date = start.split("-")[0] + "-" + start.split("-")[1] + "-" + event_start.split("-")[2] # last day of month issue, start from prev month! @@ -221,12 +227,13 @@ def get_events(start, end, user=None, for_reminder=False, filters=None): for i in range(int(date_diff(end, start) / 30) + 3): if getdate(date) >= getdate(start) and getdate(date) <= getdate(end) \ and getdate(date) <= getdate(repeat) and getdate(date) >= getdate(event_start): + add_event(e, date) date = add_months(start_from, i+1) remove_events.append(e) - if e.repeat_on=="Every Week": + if e.repeat_on=="Weekly": weekday = getdate(event_start).weekday() # monday is 0 start_weekday = getdate(start).weekday() @@ -236,19 +243,21 @@ def get_events(start, end, user=None, for_reminder=False, filters=None): for cnt in range(int(date_diff(end, start) / 7) + 3): if getdate(date) >= getdate(start) and getdate(date) <= getdate(end) \ - and getdate(date) <= getdate(repeat) and getdate(date) >= getdate(event_start): - add_event(e, date) + and getdate(date) <= getdate(repeat) and getdate(date) >= getdate(event_start) and e[weekdays[getdate(date).weekday()]]: + add_event(e, date) date = add_days(date, 7) remove_events.append(e) - if e.repeat_on=="Every Day": + if e.repeat_on=="Daily": + for cnt in range(date_diff(end, start) + 1): date = add_days(start, cnt) - if getdate(date) >= getdate(event_start) and getdate(date) <= getdate(end) \ - and getdate(date) <= getdate(repeat) and e[weekdays[getdate(date).weekday()]]: + if getdate(date) >= getdate(event_start) and getdate(date) <= getdate(end) and getdate(date) <= getdate(repeat): + add_event(e, date) + remove_events.append(e) for e in remove_events: From d2e6d12632a012b75dc38f9bb16944be66e84d4c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 26 Jul 2019 01:38:29 +0530 Subject: [PATCH 070/203] fix: list view filter not showing defaults --- frappe/public/js/frappe/list/base_list.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index 79555b7e48..362378c21e 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -598,7 +598,6 @@ class FilterArea { } const doctype_fields = this.list_view.meta.fields; - let session_defaults = frappe.db.get_single_value('Session Default Settings', 'session_defaults'); fields = fields.concat(doctype_fields.filter( df => df.in_standard_filter && frappe.model.is_value_type(df.fieldtype) @@ -617,7 +616,10 @@ class FilterArea { options = options.join("\n"); } } - let default_value = ((fieldtype === 'Link') && (session_defaults.ref_doctype === options)) ? frappe.defaults.get_user_default(options) : null; + let default_value = (fieldtype === 'Link') ? frappe.defaults.get_user_default(options) : null; + if (['__default', '__global'].includes(default_value)) { + default_value = null; + } return { fieldtype: fieldtype, label: __(df.label), From 4bcbc03500a94816fbafea1f18c602616d2675ea Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 26 Jul 2019 09:11:28 +0530 Subject: [PATCH 071/203] chore: align sql query --- frappe/desk/doctype/event/event.py | 66 ++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/frappe/desk/doctype/event/event.py b/frappe/desk/doctype/event/event.py index b9bab0a204..3558761c65 100644 --- a/frappe/desk/doctype/event/event.py +++ b/frappe/desk/doctype/event/event.py @@ -144,26 +144,58 @@ def get_events(start, end, user=None, for_reminder=False, filters=None): if isinstance(filters, string_types): filters = json.loads(filters) - events = frappe.db.sql("""select `name`, subject, description, color, - starts_on, ends_on, owner, all_day, event_type, repeat_this_event, repeat_on,repeat_till, - monday, tuesday, wednesday, thursday, friday, saturday, sunday - from `tabEvent` where (( - (date(starts_on) between date(%(start)s) and date(%(end)s)) - or (date(ends_on) between date(%(start)s) and date(%(end)s)) - or (date(starts_on) <= date(%(start)s) and date(ends_on) >= date(%(end)s)) - ) or ( - date(starts_on) <= date(%(start)s) and repeat_this_event=1 and - coalesce(repeat_till, '3000-01-01') > date(%(start)s) - )) + events = frappe.db.sql(""" + SELECT `tabEvent`.name, + `tabEvent`.subject, + `tabEvent`.description, + `tabEvent`.color, + `tabEvent`.starts_on, + `tabEvent`.ends_on, + `tabEvent`.owner, + `tabEvent`.all_day, + `tabEvent`.event_type, + `tabEvent`.repeat_this_event, + `tabEvent`.repeat_on, + `tabEvent`.repeat_till, + `tabEvent`.monday, + `tabEvent`.tuesday, + `tabEvent`.wednesday, + `tabEvent`.thursday, + `tabEvent`.friday, + `tabEvent`.saturday, + `tabEvent`.sunday + FROM `tabEvent` + WHERE ( + ( + (date(`tabEvent`.starts_on) BETWEEN date(%(start)s) AND date(%(end)s)) + OR (date(`tabEvent`.ends_on) BETWEEN date(%(start)s) AND date(%(end)s)) + OR ( + date(`tabEvent`.starts_on) <= date(%(start)s) + AND date(`tabEvent`.ends_on) >= date(%(end)s) + ) + ) + OR ( + date(`tabEvent`.starts_on) <= date(%(start)s) + AND `tabEvent`.repeat_this_event=1 + AND coalesce(`tabEvent`.repeat_till, '3000-01-01') > date(%(start)s) + ) + ) {reminder_condition} {filter_condition} - and (event_type='Public' or owner=%(user)s - or exists(select name from `tabDocShare` where - `tabDocShare`.share_doctype='Event' and `tabDocShare`.share_name=`tabEvent`.`name` - and `tabDocShare`.`user`=%(user)s)) - order by starts_on""".format( + AND ( + `tabEvent`.event_type='Public' + OR `tabEvent`.owner=%(user)s + OR EXISTS( + SELECT `tabDocShare`.name + FROM `tabDocShare` + WHERE `tabDocShare`.share_doctype='Event' + AND `tabDocShare`.share_name=`tabEvent`.name + AND `tabDocShare`.user=%(user)s + ) + ) + ORDER BY `tabEvent`.starts_on""".format( filter_condition=get_filters_cond('Event', filters, []), - reminder_condition="and coalesce(send_reminder, 0)=1" if for_reminder else "" + reminder_condition="AND coalesce(`tabEvent`.send_reminder, 0)=1" if for_reminder else "" ), { "start": start, "end": end, From 7ccff354652712c47fde1824138d5c212b19b2a1 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 26 Jul 2019 09:25:48 +0530 Subject: [PATCH 072/203] tests: fix repeat_on frequency --- .../connectors/calendar_connector.py | 11 +++++------ .../test_data_migration_run.py | 2 +- frappe/desk/doctype/event/event.py | 3 +++ frappe/desk/doctype/event/test_event.py | 2 +- .../gcalendar_to_event/__init__.py | 18 +++++++++--------- frappe/tests/test_global_search.py | 6 +++--- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/frappe/data_migration/doctype/data_migration_connector/connectors/calendar_connector.py b/frappe/data_migration/doctype/data_migration_connector/connectors/calendar_connector.py index 578ac0fc37..2c98836a1f 100644 --- a/frappe/data_migration/doctype/data_migration_connector/connectors/calendar_connector.py +++ b/frappe/data_migration/doctype/data_migration_connector/connectors/calendar_connector.py @@ -214,7 +214,9 @@ class CalendarConnector(BaseConnection): end_date = None day = [] - if e.repeat_on == "Every Day": + if e.repeat_on == "Daily": + frequency = "FREQ=DAILY" + elif e.repeat_on == "Weekly": if e.monday == 1: day.append("MO") if e.tuesday == 1: @@ -232,13 +234,10 @@ class CalendarConnector(BaseConnection): day = "BYDAY=" + ",".join(str(d) for d in day) frequency = "FREQ=WEEKLY" - - elif e.repeat_on == "Every Week": - frequency = "FREQ=WEEKLY" - elif e.repeat_on == "Every Month": + elif e.repeat_on == "Monthly": frequency = "FREQ=MONTHLY;BYDAY=SU,MO,TU,WE,TH,FR,SA;BYSETPOS=-1" end_date = datetime.combine(add_days(e.repeat_till, 1), datetime.min.time()).strftime('UNTIL=%Y%m%dT%H%M%SZ') - elif e.repeat_on == "Every Year": + elif e.repeat_on == "Yearly": frequency = "FREQ=YEARLY" else: return None diff --git a/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.py b/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.py index 07cb8aa278..ba4a255b97 100644 --- a/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.py +++ b/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.py @@ -18,7 +18,7 @@ class TestDataMigrationRun(unittest.TestCase): frappe.get_doc(dict( doctype='Event', subject=event_subject, - repeat_on='Every Month', + repeat_on='Monthly', starts_on=frappe.utils.now_datetime() )).insert() diff --git a/frappe/desk/doctype/event/event.py b/frappe/desk/doctype/event/event.py index 3558761c65..466654b141 100644 --- a/frappe/desk/doctype/event/event.py +++ b/frappe/desk/doctype/event/event.py @@ -19,6 +19,9 @@ communication_mapping = {"": "Event", "Event": "Event", "Meeting": "Meeting", "C class Event(Document): def validate(self): + if not self.starts_on: + self.starts_on = now_datetime() + if self.starts_on and self.ends_on and get_datetime(self.starts_on) > get_datetime(self.ends_on): frappe.throw(_("Event's End On cannot be before Start On.")) diff --git a/frappe/desk/doctype/event/test_event.py b/frappe/desk/doctype/event/test_event.py index 1289b0a066..6d1e865a45 100644 --- a/frappe/desk/doctype/event/test_event.py +++ b/frappe/desk/doctype/event/test_event.py @@ -112,7 +112,7 @@ class TestEvent(unittest.TestCase): "starts_on": "2014-02-01", "event_type": "Public", "repeat_this_event": 1, - "repeat_on": "Every Year" + "repeat_on": "Yearly" }) ev.insert() diff --git a/frappe/integrations/data_migration_mapping/gcalendar_to_event/__init__.py b/frappe/integrations/data_migration_mapping/gcalendar_to_event/__init__.py index 5518871c97..20cd303e9c 100644 --- a/frappe/integrations/data_migration_mapping/gcalendar_to_event/__init__.py +++ b/frappe/integrations/data_migration_mapping/gcalendar_to_event/__init__.py @@ -73,10 +73,10 @@ def get_recurrence_event_fields_value(recur_rule, starts_on): for _str in recur_rule.split(";"): if "RRULE:FREQ" in _str: repeat_every = _str.split("=")[1] - if repeat_every == "DAILY": repeat_on = "Every Day" - elif repeat_every == "WEEKLY": repeat_on = "Every Week" - elif repeat_every == "MONTHLY": repeat_on = "Every Month" - else: repeat_on = "Every Year" + if repeat_every == "DAILY": repeat_on = "Daily" + elif repeat_every == "WEEKLY": repeat_on = "Weekly" + elif repeat_every == "MONTHLY": repeat_on = "Monthly" + else: repeat_on = "Yearly" elif "UNTIL" in _str: # get repeat till date = parse(_str.split("=")[1]) @@ -96,7 +96,7 @@ def get_recurrence_event_fields_value(recur_rule, starts_on): "friday": 1 if "FR" in days else 0, "saturday": 1 if "SA" in days else 0, }) - repeat_on = "Every Day" + repeat_on = "Weekly" recurrence = { "repeat_on": repeat_on, @@ -112,16 +112,16 @@ def get_recurrence_event_fields_value(recur_rule, starts_on): def get_repeat_till_date(date, count=None, repeat_on=None): if count: - if repeat_on == "Every Day": + if repeat_on == "Daily": # add days date = date + timedelta(days=int(count)) - elif repeat_on == "Every Week": + elif repeat_on == "Weekly": # add weeks date = date + timedelta(weeks=int(count)) - elif repeat_on == "Every Month": + elif repeat_on == "Monthly": # add months date = add_months(date, int(count)) - elif repeat_on == "Every Year": + elif repeat_on == "Yearly": # add years date = add_months(date, int(count) * 12) else: diff --git a/frappe/tests/test_global_search.py b/frappe/tests/test_global_search.py index bdc32e6671..940a424966 100644 --- a/frappe/tests/test_global_search.py +++ b/frappe/tests/test_global_search.py @@ -39,7 +39,7 @@ class TestGlobalSearch(unittest.TestCase): frappe.get_doc(dict( doctype='Event', subject=text, - repeat_on='Every Month', + repeat_on='Monthly', starts_on=frappe.utils.now_datetime())).insert() global_search.sync_global_search() @@ -70,13 +70,13 @@ class TestGlobalSearch(unittest.TestCase): def test_update_fields(self): self.insert_test_events() - results = global_search.search('Every Month') + results = global_search.search('Monthly') self.assertEqual(len(results), 0) doctype = "Event" from frappe.custom.doctype.property_setter.property_setter import make_property_setter make_property_setter(doctype, "repeat_on", "in_global_search", 1, "Int") global_search.rebuild_for_doctype(doctype) - results = global_search.search('Every Month') + results = global_search.search('Monthly') self.assertEqual(len(results), 3) def test_delete_doc(self): From b7d23e330cb5f22acd70fe0c1659a9925168c846 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 26 Jul 2019 09:54:03 +0530 Subject: [PATCH 073/203] fix: add google calendar fields to events --- frappe/integrations/doctype/google_calendar/google_calendar.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index a0bab99826..54a2b34fdd 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -277,7 +277,8 @@ def get_events(doc, method=None, page_length=10): # "thursday": event.get("thursday"), # "friday": event.get("friday"), # "saturday": event.get("saturday"), - # "google_calendar_id": event.get("id") + # "google_calendar_id": account.google_calendar_id, + # "google_event_id": event.get("id"), # }).insert(ignore_permissions=True) def insert_events(doc, method=None): From 4f6f62972f25dc0317fb59c61a30d8ecb66217b7 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 26 Jul 2019 10:00:28 +0530 Subject: [PATCH 074/203] fix: remove google calendar fields --- frappe/desk/doctype/event/event.json | 31 ++-------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/frappe/desk/doctype/event/event.json b/frappe/desk/doctype/event/event.json index 24237be32a..956c87bb07 100644 --- a/frappe/desk/doctype/event/event.json +++ b/frappe/desk/doctype/event/event.json @@ -32,11 +32,7 @@ "section_break_6", "description", "participants", - "event_participants", - "sb_00", - "google_calendar_name", - "section_break_30", - "google_calendar_event" + "event_participants" ], "fields": [ { @@ -210,34 +206,11 @@ "fieldtype": "Table", "label": "Event Participants", "options": "Event Participants" - }, - { - "collapsible": 1, - "depends_on": "eval:doc.google_calendar_name && doc.google_calendar_event", - "fieldname": "sb_00", - "fieldtype": "Section Break", - "label": "Google Calendar" - }, - { - "fieldname": "google_calendar_name", - "fieldtype": "Data", - "label": "Google Calendar Name", - "read_only": 1 - }, - { - "fieldname": "section_break_30", - "fieldtype": "Column Break" - }, - { - "fieldname": "google_calendar_event", - "fieldtype": "Data", - "label": "Google Calendar Event", - "read_only": 1 } ], "icon": "fa fa-calendar", "idx": 1, - "modified": "2019-07-26 00:01:18.005707", + "modified": "2019-07-26 10:00:14.014506", "modified_by": "Administrator", "module": "Desk", "name": "Event", From 570239cd48b32bb6e0042e6f3471200ae05df6d2 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 26 Jul 2019 10:51:06 +0530 Subject: [PATCH 075/203] fix: Patch to create file in right location --- frappe/core/doctype/file/file.py | 15 +++++---- .../patches/v12_0/fix_public_private_files.py | 33 +++++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 frappe/patches/v12_0/fix_public_private_files.py diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 6e6cce52bf..8fac317100 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -473,7 +473,7 @@ class File(NestedSet): return None - def save_file(self, content=None, decode=False): + def save_file(self, content=None, decode=False, ignore_existing_file_check=False): file_exists = False self.content = content if decode: @@ -490,12 +490,15 @@ class File(NestedSet): self.content_hash = get_content_hash(self.content) self.content_type = mimetypes.guess_type(self.file_name)[0] + _file = False + # check if a file exists with the same content hash and is also in the same folder (public or private) - _file = frappe.get_value("File", { - "content_hash": self.content_hash, - "is_private": self.is_private - }, - ["file_url"]) + if not ignore_existing_file_check: + _file = frappe.get_value("File", { + "content_hash": self.content_hash, + "is_private": self.is_private + }, + ["file_url"]) if _file: self.file_url = _file diff --git a/frappe/patches/v12_0/fix_public_private_files.py b/frappe/patches/v12_0/fix_public_private_files.py new file mode 100644 index 0000000000..001f9a2d2e --- /dev/null +++ b/frappe/patches/v12_0/fix_public_private_files.py @@ -0,0 +1,33 @@ +import frappe +import os + +def execute(): + files = frappe.get_all('File', + fields=['is_private', 'file_url', 'name'], + filters={'is_folder': 0}) + + for file in files: + file_url = file.file_url + if file.is_private: + if not file_url.startswith('/private/files/'): + generate_file(file.name) + else: + if file_url.startswith('/private/files/'): + generate_file(file.name) + +def generate_file(file_name): + try: + file_doc = frappe.get_doc('File', file_name) + # private + new_doc = frappe.new_doc('File') + new_doc.is_private = file_doc.is_private + new_doc.file_name = file_doc.file_name + # to create copy of file in right location + # if the file doc is private file will be created in /private folder + # if the file doc is public file will be created in /files folder + new_doc.save_file(content=file_doc.get_content(), ignore_existing_file_check=True) + + file_doc.file_url = new_doc.file_url + file_doc.save() + except IOError: + pass \ No newline at end of file From 2020f797714b24f317223b53008cf33f252be63c Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 26 Jul 2019 11:11:23 +0530 Subject: [PATCH 076/203] fix: Typo --- frappe/patches/v12_0/fix_public_private_files.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/patches/v12_0/fix_public_private_files.py b/frappe/patches/v12_0/fix_public_private_files.py index 001f9a2d2e..3f8f30379e 100644 --- a/frappe/patches/v12_0/fix_public_private_files.py +++ b/frappe/patches/v12_0/fix_public_private_files.py @@ -23,8 +23,8 @@ def generate_file(file_name): new_doc.is_private = file_doc.is_private new_doc.file_name = file_doc.file_name # to create copy of file in right location - # if the file doc is private file will be created in /private folder - # if the file doc is public file will be created in /files folder + # if the file doc is private then the file will be created in /private folder + # if the file doc is public then the file will be created in /files folder new_doc.save_file(content=file_doc.get_content(), ignore_existing_file_check=True) file_doc.file_url = new_doc.file_url From 65884a0a3ed4d5cedf248109579ab25ab8f5ae7a Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Fri, 26 Jul 2019 13:06:32 +0550 Subject: [PATCH 077/203] bumped to version 12.0.1 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 62576bea8e..c2008a059d 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -23,7 +23,7 @@ if sys.version[0] == '2': reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '12.0.0' +__version__ = '12.0.1' __title__ = "Frappe Framework" local = Local() From 577f3c8897530c5cf054a6a420045f01c624f71f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 26 Jul 2019 14:27:14 +0530 Subject: [PATCH 078/203] fix: redirect for OAuth --- frappe/public/js/frappe/router.js | 4 ++++ frappe/utils/oauth.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js index ceb060530d..ad170b295e 100644 --- a/frappe/public/js/frappe/router.js +++ b/frappe/public/js/frappe/router.js @@ -47,6 +47,10 @@ frappe.route = function() { if(route[0]) { const title_cased_route = frappe.utils.to_title_case(route[0]); + if (title_cased_route === 'Desktop') { + frappe.views.pageview.show(''); + } + if(route[1] && frappe.views[title_cased_route + "Factory"]) { // has a view generator, generate! if(!frappe.view_factory[title_cased_route]) { diff --git a/frappe/utils/oauth.py b/frappe/utils/oauth.py index f5cf5d81e8..9c30a04e63 100644 --- a/frappe/utils/oauth.py +++ b/frappe/utils/oauth.py @@ -290,7 +290,7 @@ def redirect_post_login(desk_user): frappe.local.response["type"] = "redirect" # the #desktop is added to prevent a facebook redirect bug - frappe.local.response["location"] = "/desk#desktop" if desk_user else "/" + frappe.local.response["location"] = "/desk#desktop" if desk_user else "/me" def oauth_decoder(data): if isinstance(data, bytes): From 02e2d17d71b613b62b1a9c44faf8cbde326833b5 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Fri, 26 Jul 2019 14:57:46 +0530 Subject: [PATCH 079/203] fix(dashboards): Map Chart Type Average to aggregate function AVG (#8006) Co-authored-by: adityahase --- frappe/desk/doctype/dashboard_chart/dashboard_chart.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 53d1110e83..73677b1a95 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -45,7 +45,7 @@ def get(chart_name, from_date=None, to_date=None, refresh = None): '''.format( unit_function = get_unit_function(chart.based_on, timegrain), datefield = chart.based_on, - aggregate_function = chart.chart_type, + aggregate_function = get_aggregate_function(chart.chart_type), value_field = chart.value_based_on or '1', doctype = chart.document_type, conditions = conditions, @@ -67,6 +67,13 @@ def get(chart_name, from_date=None, to_date=None, refresh = None): }] } +def get_aggregate_function(chart_type): + return { + "Sum": "SUM", + "Count": "COUNT", + "Average": "AVG" + }[chart_type] + def convert_to_dates(data, timegrain): result = [] for d in data: From 72427d0b2577095c01a5eeeecc706aa5b085efc0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 26 Jul 2019 12:31:42 +0530 Subject: [PATCH 080/203] fix: Unknown string format:', u'Total' --- frappe/templates/emails/auto_email_report.html | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frappe/templates/emails/auto_email_report.html b/frappe/templates/emails/auto_email_report.html index 50ab5b1fe3..a658c988a9 100644 --- a/frappe/templates/emails/auto_email_report.html +++ b/frappe/templates/emails/auto_email_report.html @@ -33,9 +33,15 @@ {% for row in data %} {% for col in columns %} - - {{- frappe.format(row[col.fieldname], col, row) -}} - + {% if row[col.fieldname] == 'Total' %} + + {{- row[col.fieldname] -}} + + {% else %} + + {{- frappe.format(row[col.fieldname], col, row) -}} + + {% endif %} {% endfor %} {% endfor %} From 03e300289a82227afad64975234f8d16d896dc2d Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 26 Jul 2019 15:14:22 +0530 Subject: [PATCH 081/203] fix: added default parameters in make_auto_repeat --- frappe/automation/doctype/auto_repeat/auto_repeat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.py b/frappe/automation/doctype/auto_repeat/auto_repeat.py index 8a429b93f9..0ad0a9e9c6 100644 --- a/frappe/automation/doctype/auto_repeat/auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/auto_repeat.py @@ -325,7 +325,7 @@ def set_auto_repeat_as_completed(): doc.save() @frappe.whitelist() -def make_auto_repeat(doctype, docname, frequency, start_date, end_date = None): +def make_auto_repeat(doctype, docname, frequency = 'Daily', start_date = today(), end_date = None): doc = frappe.new_doc('Auto Repeat') doc.reference_doctype = doctype doc.reference_document = docname From 2f76e70f4043527758209bfb5d63813c9c4b2dd3 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 26 Jul 2019 15:15:37 +0530 Subject: [PATCH 082/203] fix: end date for auto repeat --- frappe/automation/doctype/auto_repeat/auto_repeat.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.py b/frappe/automation/doctype/auto_repeat/auto_repeat.py index 0ad0a9e9c6..71bf9cfeb9 100644 --- a/frappe/automation/doctype/auto_repeat/auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/auto_repeat.py @@ -61,7 +61,8 @@ class AutoRepeat(Document): frappe.throw(_("Enable Allow Auto Repeat for the doctype {0} in Customize Form").format(self.reference_doctype)) def validate_dates(self): - self.validate_from_to_dates('start_date', 'end_date') + if self.end_date: + self.validate_from_to_dates('start_date', 'end_date') if self.end_date == self.start_date: frappe.throw(_('{0} should not be same as {1}').format(frappe.bold('End Date'), frappe.bold('Start Date'))) From 8dd180ae822691f860b144511ced56da1c5d09f1 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 26 Jul 2019 16:03:33 +0530 Subject: [PATCH 083/203] fix: removed dynamic value in default parameter --- frappe/automation/doctype/auto_repeat/auto_repeat.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.py b/frappe/automation/doctype/auto_repeat/auto_repeat.py index 71bf9cfeb9..334862f27a 100644 --- a/frappe/automation/doctype/auto_repeat/auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/auto_repeat.py @@ -326,7 +326,9 @@ def set_auto_repeat_as_completed(): doc.save() @frappe.whitelist() -def make_auto_repeat(doctype, docname, frequency = 'Daily', start_date = today(), end_date = None): +def make_auto_repeat(doctype, docname, frequency = 'Daily', start_date = None, end_date = None): + if not start_date: + start_date = getdate(today()) doc = frappe.new_doc('Auto Repeat') doc.reference_doctype = doctype doc.reference_document = docname From 4c14574270469ed223758e41bf087e9044f39208 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 26 Jul 2019 18:04:53 +0530 Subject: [PATCH 084/203] fix: date comparison fix --- frappe/automation/doctype/auto_repeat/auto_repeat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.py b/frappe/automation/doctype/auto_repeat/auto_repeat.py index 334862f27a..864a60e498 100644 --- a/frappe/automation/doctype/auto_repeat/auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/auto_repeat.py @@ -33,7 +33,7 @@ class AutoRepeat(Document): def before_insert(self): if not frappe.flags.in_test: start_date = self.start_date - today_date = today() + today_date = getdate(today()) if start_date <= today_date: start_date = today_date From 421e4eebeb57ffd868277b3d8b5745f7df4bd110 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 26 Jul 2019 20:16:42 +0530 Subject: [PATCH 085/203] fix: show events on the correct day --- frappe/desk/doctype/event/event.py | 52 +++++++++++------------------- 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/frappe/desk/doctype/event/event.py b/frappe/desk/doctype/event/event.py index 466654b141..d5385bf851 100644 --- a/frappe/desk/doctype/event/event.py +++ b/frappe/desk/doctype/event/event.py @@ -22,14 +22,12 @@ class Event(Document): if not self.starts_on: self.starts_on = now_datetime() - if self.starts_on and self.ends_on and get_datetime(self.starts_on) > get_datetime(self.ends_on): - frappe.throw(_("Event's End On cannot be before Start On.")) + self.validate_from_to_dates("starts_on", "ends_on") - if self.starts_on == self.ends_on: - # this scenario doesn't make sense i.e. it starts and ends at the same second! - self.ends_on = None + # if start == end this scenario doesn't make sense i.e. it starts and ends at the same second! + self.ends_on = None if self.starts_on == self.ends_on else self.ends_on - if getdate(self.starts_on) != getdate(self.ends_on) and self.repeat_on == "Daily": + if self.repeat_on == "Daily" and getdate(self.starts_on) != getdate(self.ends_on): frappe.throw(_("Daily Events should finish on the Same Day.")) def on_update(self): @@ -62,9 +60,9 @@ class Event(Document): self.create_communication(participant) def create_communication(self, participant): - communication = frappe.new_doc("Communication") - self.update_communication(participant, communication) - self.communication = communication.name + communication = frappe.new_doc("Communication") + self.update_communication(participant, communication) + self.communication = communication.name def update_communication(self, participant, communication): communication.communication_medium = "Event" @@ -116,7 +114,6 @@ def has_permission(doc, user): return False - def send_event_digest(): today = nowdate() for user in get_enabled_system_users(): @@ -216,16 +213,16 @@ def get_events(start, end, user=None, for_reminder=False, filters=None): enddate = add_days(date,int(date_diff(e.ends_on.split(" ")[0], e.starts_on.split(" ")[0]))) \ if (e.starts_on and e.ends_on) else date + new_event.starts_on = date + " " + e.starts_on.split(" ")[1] - if e.ends_on: - new_event.ends_on = enddate + " " + e.ends_on.split(" ")[1] + new_event.ends_on = new_event.ends_on = enddate + " " + e.ends_on.split(" ")[1] if e.ends_on else None + add_events.append(new_event) for e in events: if e.repeat_this_event: e.starts_on = get_datetime_str(e.starts_on) - if e.ends_on: - e.ends_on = get_datetime_str(e.ends_on) + e.ends_on = get_datetime_str(e.ends_on) if e.ends_on else None event_start, time_str = get_datetime_str(e.starts_on).split(" ") @@ -242,12 +239,11 @@ def get_events(start, end, user=None, for_reminder=False, filters=None): for year in range(start_year, end_year+1): date = str(year) + "-" + event_start if getdate(date) >= getdate(start) and getdate(date) <= getdate(end) and getdate(date) <= getdate(repeat): - add_event(e, date) remove_events.append(e) - if e.repeat_on=="Monthly": + if e.repeat_on == "Monthly": # creates a string with date (27) and month (07) and year (2019) eg: 2019-07-27 date = start.split("-")[0] + "-" + start.split("-")[1] + "-" + event_start.split("-")[2] @@ -262,35 +258,25 @@ def get_events(start, end, user=None, for_reminder=False, filters=None): for i in range(int(date_diff(end, start) / 30) + 3): if getdate(date) >= getdate(start) and getdate(date) <= getdate(end) \ and getdate(date) <= getdate(repeat) and getdate(date) >= getdate(event_start): - add_event(e, date) + date = add_months(start_from, i+1) - remove_events.append(e) - if e.repeat_on=="Weekly": - weekday = getdate(event_start).weekday() - # monday is 0 - start_weekday = getdate(start).weekday() - - # start from nearest weeday after last monday - date = add_days(start, weekday - start_weekday) - - for cnt in range(int(date_diff(end, start) / 7) + 3): + if e.repeat_on == "Weekly": + for cnt in range(date_diff(end, start) + 1): + date = add_days(start, cnt) if getdate(date) >= getdate(start) and getdate(date) <= getdate(end) \ - and getdate(date) <= getdate(repeat) and getdate(date) >= getdate(event_start) and e[weekdays[getdate(date).weekday()]]: - + and getdate(date) <= getdate(repeat) and getdate(date) >= getdate(event_start) \ + and e[weekdays[getdate(date).weekday()]]: add_event(e, date) - date = add_days(date, 7) remove_events.append(e) - if e.repeat_on=="Daily": - + if e.repeat_on == "Daily": for cnt in range(date_diff(end, start) + 1): date = add_days(start, cnt) if getdate(date) >= getdate(event_start) and getdate(date) <= getdate(end) and getdate(date) <= getdate(repeat): - add_event(e, date) remove_events.append(e) From cabb3f314b4b54cab5b5ec4f9cd33476e66e2d22 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 26 Jul 2019 20:48:45 +0530 Subject: [PATCH 086/203] fix: validate dates only if starts_on and ends_on --- frappe/desk/doctype/event/event.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/desk/doctype/event/event.py b/frappe/desk/doctype/event/event.py index d5385bf851..473076fc2c 100644 --- a/frappe/desk/doctype/event/event.py +++ b/frappe/desk/doctype/event/event.py @@ -22,7 +22,8 @@ class Event(Document): if not self.starts_on: self.starts_on = now_datetime() - self.validate_from_to_dates("starts_on", "ends_on") + if self.starts_on and self.ends_on: + self.validate_from_to_dates("starts_on", "ends_on") # if start == end this scenario doesn't make sense i.e. it starts and ends at the same second! self.ends_on = None if self.starts_on == self.ends_on else self.ends_on From ce60f98ab68e72afcf2621fc3da515de9ca22fc9 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Fri, 26 Jul 2019 20:49:46 +0530 Subject: [PATCH 087/203] Revert "fix(security): Disallow unnecessary characters in group_by and fields" This reverts commit fb8993663c2e587d78667937ad356d17610a2214. --- frappe/model/db_query.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 7767d2a021..53a1c6c13d 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -240,9 +240,6 @@ class DatabaseQuery(object): _is_query(field) - invalid_characters_regex = r".*[^a-zA-Z0-9-_ ,`'\"\*\.\(\)].*" - if re.match(invalid_characters_regex, field): - frappe.throw(_("Illegal characters in SQL query")) def extract_tables(self): """extract tables from fields""" @@ -691,9 +688,6 @@ class DatabaseQuery(object): if 'select' in _lower and ' from ' in _lower: frappe.throw(_('Cannot use sub-query in order by')) - invalid_characters_regex = r".*[^a-z0-9-_ ,`'\"\.\(\)].*" - if re.match(invalid_characters_regex, _lower): - frappe.throw(_("Illegal characters in SQL query")) for field in parameters.split(","): if "." in field and field.strip().startswith("`tab"): From fb896a460ee33200e21751f836afcec21fedc0c4 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Fri, 26 Jul 2019 20:49:56 +0530 Subject: [PATCH 088/203] Revert "fix(security): Make jinja rendering tighter" This reverts commit b30199b7f5c9dc62563fc4c528432f1160de0fd3. --- frappe/utils/jinja.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py index f8745b82c3..7a27fb3c3b 100644 --- a/frappe/utils/jinja.py +++ b/frappe/utils/jinja.py @@ -71,7 +71,7 @@ def render_template(template, context, is_path=None, safe_render=True): or (template.endswith('.html') and '\n' not in template)): return get_jenv().get_template(template).render(context) else: - if safe_render and "__" in template: + if safe_render and ".__" in template: throw("Illegal template") try: return get_jenv().from_string(template).render(context) From 067c6f8a35ac8f4b721a22416069e9f8caa8cc29 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 26 Jul 2019 22:40:35 +0530 Subject: [PATCH 089/203] patch: migrate to newer repeat_on --- frappe/patches.txt | 1 + .../patches/v12_0/rename_events_repeat_on.py | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 frappe/patches/v12_0/rename_events_repeat_on.py diff --git a/frappe/patches.txt b/frappe/patches.txt index 0a43328a12..6b5f246e5f 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -247,3 +247,4 @@ frappe.patches.v12_0.remove_feedback_rating frappe.patches.v12_0.move_form_attachments_to_attachments_folder frappe.patches.v12_0.move_timeline_links_to_dynamic_links frappe.patches.v12_0.delete_feedback_request_if_exists #1 +frappe.patches.v12_0.rename_events_repeat_on #1 diff --git a/frappe/patches/v12_0/rename_events_repeat_on.py b/frappe/patches/v12_0/rename_events_repeat_on.py new file mode 100644 index 0000000000..b78bf33f19 --- /dev/null +++ b/frappe/patches/v12_0/rename_events_repeat_on.py @@ -0,0 +1,32 @@ +import frappe +from frappe.utils import get_datetime + +def execute(): + weekdays = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"] + + daily_events = frappe.get_list("Event", filters={"repeat_this_event": 1, "repeat_on": "Every Day"}, fields=["name", "starts_on"]) + weekly_events = frappe.get_list("Event", filters={"repeat_this_event": 1, "repeat_on": "Every Week"}, fields=["name", "starts_on"]) + monthly_events = frappe.get_list("Event", filters={"repeat_this_event": 1, "repeat_on": "Every Month"}, fields=["name", "starts_on"]) + yearly_events = frappe.get_list("Event", filters={"repeat_this_event": 1, "repeat_on": "Every Year"}, fields=["name", "starts_on"]) + + frappe.reload_doc("desk", "doctype", "event") + + for daily_event in daily_events: + """ + Initially Daily Events had option to choose days, but now Weekly does, + so just changing from Daily -> Weekly does the job + """ + frappe.db.set_value("Event", daily_event.name, "repeat_on", "Weekly") + + for weekly_event in weekly_events: + """ + Set WeekDay based on the starts_on so that event can repeat Weekly + """ + frappe.db.set_value("Event", weekly_event.name, "repeat_on", "Weekly") + frappe.db.set_value("Event", weekly_event.name, weekdays[get_datetime(weekly_event.starts_on).weekday()], 1) + + for monthly_event in monthly_events: + frappe.db.set_value("Event", monthly_event.name, "repeat_on", "Monthly") + + for yearly_event in yearly_events: + frappe.db.set_value("Event", yearly_event.name, "repeat_on", "Yearly") \ No newline at end of file From 0fd333aaf17e82e68bdb017597f0f0555a8e7c7d Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 26 Jul 2019 22:43:12 +0530 Subject: [PATCH 090/203] fix: typo --- frappe/patches.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/patches.txt b/frappe/patches.txt index 6b5f246e5f..dc12374903 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -247,4 +247,4 @@ frappe.patches.v12_0.remove_feedback_rating frappe.patches.v12_0.move_form_attachments_to_attachments_folder frappe.patches.v12_0.move_timeline_links_to_dynamic_links frappe.patches.v12_0.delete_feedback_request_if_exists #1 -frappe.patches.v12_0.rename_events_repeat_on #1 +frappe.patches.v12_0.rename_events_repeat_on From 8b7a62ec369b42332a3860de21139b3710c04345 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 27 Jul 2019 11:05:18 +0530 Subject: [PATCH 091/203] feat: add status to event --- frappe/desk/doctype/event/event.json | 12 +++++++++++- frappe/desk/doctype/event/event.py | 9 +++++++++ frappe/desk/doctype/event/event_list.js | 7 ++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/event/event.json b/frappe/desk/doctype/event/event.json index 956c87bb07..6f40da707c 100644 --- a/frappe/desk/doctype/event/event.json +++ b/frappe/desk/doctype/event/event.json @@ -15,6 +15,7 @@ "column_break_4", "starts_on", "ends_on", + "status", "all_day", "section_break_13", "repeat_on", @@ -206,11 +207,20 @@ "fieldtype": "Table", "label": "Event Participants", "options": "Event Participants" + }, + { + "default": "Open", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "Open\nClosed" } ], "icon": "fa fa-calendar", "idx": 1, - "modified": "2019-07-26 10:00:14.014506", + "modified": "2019-07-27 10:33:39.183832", "modified_by": "Administrator", "module": "Desk", "name": "Event", diff --git a/frappe/desk/doctype/event/event.py b/frappe/desk/doctype/event/event.py index 473076fc2c..7873a0a6fd 100644 --- a/frappe/desk/doctype/event/event.py +++ b/frappe/desk/doctype/event/event.py @@ -309,3 +309,12 @@ def delete_events(ref_type, ref_name, delete_event=False): frappe.db.sql("DELETE FROM `tabEvent` WHERE `name` = %(name)s", {'name': participation.parent}) frappe.db.sql("DELETE FROM `tabEvent Participants ` WHERE `name` = %(name)s", {'name': participation.name}) + +# Close events if ends_on or repeat_till is less than now_datetime +def set_status_of_events(): + events = frappe.get_list("Event", filters={"status": "Open"}, fields=["name", "ends_on", "repeat_till"]) + for event in events: + if (event.ends_on and getdate(event.ends_on) < getdate(nowdate())) \ + or (event.repeat_till and getdate(event.repeat_till) < getdate(nowdate())): + + frappe.db.set_value("Event", event.name, "status", "Closed") \ No newline at end of file diff --git a/frappe/desk/doctype/event/event_list.js b/frappe/desk/doctype/event/event_list.js index f56035cbb1..b71d88e6d2 100644 --- a/frappe/desk/doctype/event/event_list.js +++ b/frappe/desk/doctype/event/event_list.js @@ -1,3 +1,8 @@ frappe.listview_settings['Event'] = { - add_fields: ["starts_on", "ends_on"] + add_fields: ["starts_on", "ends_on"], + onload: function(listview) { + frappe.route_options = { + "status": "Open" + }; + } } \ No newline at end of file From 1fd05ff0a5cf92f6f84229efc4069f9246dbfc89 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 27 Jul 2019 11:21:29 +0530 Subject: [PATCH 092/203] fix: codacy --- frappe/desk/doctype/event/event.py | 6 +++--- frappe/desk/doctype/event/event_list.js | 2 +- frappe/patches/v12_0/rename_events_repeat_on.py | 4 +--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/frappe/desk/doctype/event/event.py b/frappe/desk/doctype/event/event.py index 7873a0a6fd..b1306b432e 100644 --- a/frappe/desk/doctype/event/event.py +++ b/frappe/desk/doctype/event/event.py @@ -22,12 +22,12 @@ class Event(Document): if not self.starts_on: self.starts_on = now_datetime() - if self.starts_on and self.ends_on: - self.validate_from_to_dates("starts_on", "ends_on") - # if start == end this scenario doesn't make sense i.e. it starts and ends at the same second! self.ends_on = None if self.starts_on == self.ends_on else self.ends_on + if self.starts_on and self.ends_on: + self.validate_from_to_dates("starts_on", "ends_on") + if self.repeat_on == "Daily" and getdate(self.starts_on) != getdate(self.ends_on): frappe.throw(_("Daily Events should finish on the Same Day.")) diff --git a/frappe/desk/doctype/event/event_list.js b/frappe/desk/doctype/event/event_list.js index b71d88e6d2..5d73d9dd1a 100644 --- a/frappe/desk/doctype/event/event_list.js +++ b/frappe/desk/doctype/event/event_list.js @@ -1,6 +1,6 @@ frappe.listview_settings['Event'] = { add_fields: ["starts_on", "ends_on"], - onload: function(listview) { + onload: function() { frappe.route_options = { "status": "Open" }; diff --git a/frappe/patches/v12_0/rename_events_repeat_on.py b/frappe/patches/v12_0/rename_events_repeat_on.py index b78bf33f19..789610e14d 100644 --- a/frappe/patches/v12_0/rename_events_repeat_on.py +++ b/frappe/patches/v12_0/rename_events_repeat_on.py @@ -19,9 +19,7 @@ def execute(): frappe.db.set_value("Event", daily_event.name, "repeat_on", "Weekly") for weekly_event in weekly_events: - """ - Set WeekDay based on the starts_on so that event can repeat Weekly - """ + # Set WeekDay based on the starts_on so that event can repeat Weekly frappe.db.set_value("Event", weekly_event.name, "repeat_on", "Weekly") frappe.db.set_value("Event", weekly_event.name, weekdays[get_datetime(weekly_event.starts_on).weekday()], 1) From 26826441c80b9b94b7aac3e68d5857113584bd7a Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 27 Jul 2019 11:23:22 +0530 Subject: [PATCH 093/203] fix: test case for customize form --- frappe/custom/doctype/customize_form/test_customize_form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/custom/doctype/customize_form/test_customize_form.py b/frappe/custom/doctype/customize_form/test_customize_form.py index 7da63f4d5d..4547e81e4e 100644 --- a/frappe/custom/doctype/customize_form/test_customize_form.py +++ b/frappe/custom/doctype/customize_form/test_customize_form.py @@ -46,7 +46,7 @@ class TestCustomizeForm(unittest.TestCase): d = self.get_customize_form("Event") self.assertEquals(d.doc_type, "Event") - self.assertEquals(len(d.get("fields")), 28) + self.assertEquals(len(d.get("fields")), 29) d = self.get_customize_form("Event") self.assertEquals(d.doc_type, "Event") From 0ce42718f47a8d56d353c3c8970d95417c4b41f6 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Sat, 27 Jul 2019 11:51:26 +0530 Subject: [PATCH 094/203] Update rename_events_repeat_on.py --- frappe/patches/v12_0/rename_events_repeat_on.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/frappe/patches/v12_0/rename_events_repeat_on.py b/frappe/patches/v12_0/rename_events_repeat_on.py index 789610e14d..81ba620329 100644 --- a/frappe/patches/v12_0/rename_events_repeat_on.py +++ b/frappe/patches/v12_0/rename_events_repeat_on.py @@ -12,10 +12,7 @@ def execute(): frappe.reload_doc("desk", "doctype", "event") for daily_event in daily_events: - """ - Initially Daily Events had option to choose days, but now Weekly does, - so just changing from Daily -> Weekly does the job - """ + # Initially Daily Events had option to choose days, but now Weekly does, so just changing from Daily -> Weekly does the job frappe.db.set_value("Event", daily_event.name, "repeat_on", "Weekly") for weekly_event in weekly_events: @@ -27,4 +24,4 @@ def execute(): frappe.db.set_value("Event", monthly_event.name, "repeat_on", "Monthly") for yearly_event in yearly_events: - frappe.db.set_value("Event", yearly_event.name, "repeat_on", "Yearly") \ No newline at end of file + frappe.db.set_value("Event", yearly_event.name, "repeat_on", "Yearly") From b7ee413412d4480b6c97ccc45ec1fff80dad806e Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Sat, 27 Jul 2019 13:12:07 +0550 Subject: [PATCH 095/203] bumped to version 12.0.2 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index c2008a059d..a761daa143 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -23,7 +23,7 @@ if sys.version[0] == '2': reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '12.0.1' +__version__ = '12.0.2' __title__ = "Frappe Framework" local = Local() From 84aff14cd9a32c5378fec27e0f22199f7fc71d23 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 27 Jul 2019 15:28:35 +0530 Subject: [PATCH 096/203] feat: map google calendar to frappe --- frappe/desk/doctype/event/event.json | 1144 ++++------------- frappe/hooks.py | 6 +- .../google_calendar/google_calendar.py | 268 ++-- 3 files changed, 412 insertions(+), 1006 deletions(-) diff --git a/frappe/desk/doctype/event/event.json b/frappe/desk/doctype/event/event.json index d20d678a0e..03375796c4 100644 --- a/frappe/desk/doctype/event/event.json +++ b/frappe/desk/doctype/event/event.json @@ -1,944 +1,278 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "EV.#####", - "beta": 0, - "creation": "2013-06-10 13:17:47", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, - "engine": "InnoDB", + "allow_import": 1, + "autoname": "EV.#####", + "creation": "2013-06-10 13:17:47", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "details", + "subject", + "event_category", + "event_type", + "send_reminder", + "repeat_this_event", + "column_break_4", + "starts_on", + "ends_on", + "all_day", + "section_break_13", + "repeat_on", + "repeat_till", + "column_break_16", + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday", + "section_break_8", + "color", + "section_break_6", + "description", + "participants", + "event_participants", + "sb_00", + "google_calendar_id", + "cb_00", + "google_event_id" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "details", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "details", + "fieldtype": "Section Break", + "label": "Details", + "oldfieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "subject", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Subject", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "subject", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Subject", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "event_category", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Event Category", - "length": 0, - "no_copy": 0, - "options": "Event\nMeeting\nCall\nSent/Received Email\nOther", - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "event_category", + "fieldtype": "Select", + "label": "Event Category", + "options": "Event\nMeeting\nCall\nSent/Received Email\nOther" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "event_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Event Type", - "length": 0, - "no_copy": 0, - "oldfieldname": "event_type", - "oldfieldtype": "Select", - "options": "Private\nPublic\nCancelled", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "event_type", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Event Type", + "oldfieldname": "event_type", + "oldfieldtype": "Select", + "options": "Private\nPublic\nCancelled", + "reqd": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fieldname": "send_reminder", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Send an email reminder in the morning", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "1", + "fieldname": "send_reminder", + "fieldtype": "Check", + "label": "Send an email reminder in the morning" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "repeat_this_event", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Repeat this Event", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "repeat_this_event", + "fieldtype": "Check", + "label": "Repeat this Event" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "starts_on", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Starts on", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "starts_on", + "fieldtype": "Datetime", + "label": "Starts on", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "ends_on", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Ends on", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "ends_on", + "fieldtype": "Datetime", + "label": "Ends on" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "all_day", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "All Day", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "all_day", + "fieldtype": "Check", + "label": "All Day" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "repeat_this_event", - "fieldname": "section_break_13", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "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, - "translatable": 0, - "unique": 0 - }, + "depends_on": "repeat_this_event", + "fieldname": "section_break_13", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "repeat_this_event", - "fieldname": "repeat_on", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Repeat On", - "length": 0, - "no_copy": 0, - "options": "\nEvery Day\nEvery Week\nEvery Month\nEvery Year", - "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, - "translatable": 0, - "unique": 0 - }, + "depends_on": "repeat_this_event", + "fieldname": "repeat_on", + "fieldtype": "Select", + "in_global_search": 1, + "label": "Repeat On", + "options": "\nEvery Day\nEvery Week\nEvery Month\nEvery Year" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "repeat_this_event", - "description": "Leave blank to repeat always", - "fieldname": "repeat_till", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Repeat Till", - "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, - "translatable": 0, - "unique": 0 - }, + "depends_on": "repeat_this_event", + "description": "Leave blank to repeat always", + "fieldname": "repeat_till", + "fieldtype": "Date", + "label": "Repeat Till" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_16", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_16", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", - "fieldname": "monday", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Monday", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", + "fieldname": "monday", + "fieldtype": "Check", + "label": "Monday" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", - "fieldname": "tuesday", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Tuesday", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", + "fieldname": "tuesday", + "fieldtype": "Check", + "label": "Tuesday" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", - "fieldname": "wednesday", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Wednesday", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", + "fieldname": "wednesday", + "fieldtype": "Check", + "label": "Wednesday" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", - "fieldname": "thursday", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Thursday", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", + "fieldname": "thursday", + "fieldtype": "Check", + "label": "Thursday" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", - "fieldname": "friday", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Friday", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", + "fieldname": "friday", + "fieldtype": "Check", + "label": "Friday" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", - "fieldname": "saturday", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Saturday", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", + "fieldname": "saturday", + "fieldtype": "Check", + "label": "Saturday" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", - "fieldname": "sunday", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sunday", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:doc.repeat_this_event && doc.repeat_on===\"Every Day\"", + "fieldname": "sunday", + "fieldtype": "Check", + "label": "Sunday" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_8", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_8", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "color", - "fieldtype": "Color", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Color", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "color", + "fieldtype": "Color", + "label": "Color" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "oldfieldname": "description", - "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "300px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "description", + "fieldtype": "Text Editor", + "in_global_search": 1, + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "print_width": "300px", "width": "300px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "participants", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Participants", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "participants", + "fieldtype": "Section Break", + "label": "Participants", + "oldfieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "event_participants", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Event Participants", - "length": 0, - "no_copy": 0, - "options": "Event Participants", - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 + "fieldname": "event_participants", + "fieldtype": "Table", + "label": "Event Participants", + "options": "Event Participants" + }, + { + "collapsible": 1, + "depends_on": "eval:doc.google_calendar_id && doc.google_event_id", + "fieldname": "sb_00", + "fieldtype": "Section Break", + "label": "Google Calendar" + }, + { + "fieldname": "google_calendar_id", + "fieldtype": "Data", + "label": "Google Calendar Id", + "read_only": 1 + }, + { + "fieldname": "cb_00", + "fieldtype": "Column Break" + }, + { + "fieldname": "google_event_id", + "fieldtype": "Data", + "label": "Google Event Id", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-calendar", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-09-25 12:40:33.958600", - "modified_by": "Administrator", - "module": "Desk", - "name": "Event", - "owner": "Administrator", + ], + "icon": "fa fa-calendar", + "idx": 1, + "modified": "2019-07-27 12:39:19.246328", + "modified_by": "Administrator", + "module": "Desk", + "name": "Event", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "All", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 1, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 1, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_order": "DESC", - "title_field": "subject", - "track_changes": 1, - "track_seen": 1, + ], + "read_only": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "subject", + "track_changes": 1, + "track_seen": 1, "track_views": 1 } \ No newline at end of file diff --git a/frappe/hooks.py b/frappe/hooks.py index a4c3822c47..b49e3148ac 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -143,9 +143,9 @@ doc_events = { "validate": "frappe.email.doctype.email_group.email_group.restrict_email_group" }, "Event": { - "after_insert": "frappe.integrations.doctype.google_calendar.google_calendar.insert_events", - "on_update": "frappe.integrations.doctype.google_calendar.google_calendar.update_events", - "on_trash": "frappe.integrations.doctype.google_calendar.google_calendar.delete_events", + "after_insert": "frappe.integrations.doctype.google_calendar.google_calendar.google_calendar_get_events", + "on_update": "frappe.integrations.doctype.google_calendar.google_calendar.google_calendar_update_events", + "on_trash": "frappe.integrations.doctype.google_calendar.google_calendar.google_calendar_delete_events", } } diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 54a2b34fdd..b4e4f03db2 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -12,10 +12,45 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import get_request_site_address from googleapiclient.errors import HttpError -from frappe.utils import add_days, add_years +from frappe.utils import add_days, add_years, get_datetime +from dateutil import parser SCOPES = "https://www.googleapis.com/auth/calendar/v3" +google_calendar_frequencies = { + "RRULE:FREQ=DAILY": "Every Day", + "RRULE:FREQ=WEEKLY": "Every Week", + "RRULE:FREQ=MONTHLY": "Every Month", + "RRULE:FREQ=YEARLY": "Every Year" +} + +google_calendar_days = { + "MO": "monday", + "TU": "tuesday" + "WE": "wednesday", + "TH": "thursday", + "FR": "friday", + "SA": "saturday", + "SU": "sunday" +} + +framework_frequencies = { + "Every Day": "RRULE:FREQ=DAILY", + "Every Week": "RRULE:FREQ=WEEKLY", + "Every Month": "RRULE:FREQ=MONTHLY", + "Every Year": "RRULE:FREQ=YEARLY" +} + +framework_days = { + "monday": "MO", + "tuesday": "TU" + "wednesday": "WE", + "thursday": "TH", + "friday": "FR", + "saturday": "SA", + "sunday": "SU" +} + class GoogleCalendar(Document): def validate_google_settings(self): @@ -121,7 +156,7 @@ def sync(g_calendar=None): google_calendars = frappe.get_list("Google Calendar", filters=filters) for g in google_calendars: - get_events(frappe.get_doc("Google Calendar", g.name)) + google_calendar_get_events(frappe.get_doc("Google Calendar", g.name)) def get_credentials(g_calendar): google_settings = frappe.get_doc("Google Settings") @@ -174,7 +209,7 @@ def check_remote_calendar(account, google_calendar): frappe.log_error(frappe.get_traceback(), _("Google Calendar Synchronization Error.")) -def get_events(doc, method=None, page_length=10): +def google_calendar_get_events(doc, method=None, page_length=10): """ Sync Events with Google Calendar """ @@ -188,51 +223,7 @@ def get_events(doc, method=None, page_length=10): print("1") while True: try: - """ - API Response - { - 'kind': 'calendar#events', - 'etag': '"etag"', - 'summary': 'Test Calendar', - 'updated': '2019-07-24T17:46:24.366Z', - 'timeZone': 'Asia/Kolkata', - 'accessRole': 'owner', - 'defaultReminders': [], - 'nextSyncToken': 'nextSyncToken', - 'items': [ - { - 'kind': 'calendar#event', - 'etag': '"etag"', - 'id': 'id', - 'status': 'confirmed', - 'htmlLink': 'https://www.google.com/calendar/event?eid=eid', - 'created': '2019-07-24T17:46:24.000Z', - 'updated': '2019-07-24T17:46:24.366Z', - 'summary': 'qusk', - 'creator': { - 'email': 'himanshu@iwebnotes.com' - }, - 'organizer': { - 'email': 'calendar_id', - 'displayName': 'Test Calendar', - 'self': True - }, - 'start': { - 'dateTime': '2019-07-26T20:00:00+05:30' - }, - 'end': { - 'dateTime': '2019-07-26T21:00:00+05:30' - }, - 'iCalUID': 'UID', - 'sequence': 0, - 'recurrence': ['RRULE:FREQ=DAILY'], - 'reminders': { - 'useDefault': True - } - } - ] - } - """ + # API Response listed at EOF events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, singleEvents=False, showDeleted=True, syncToken=account.next_sync_token or None).execute() print("2") @@ -258,30 +249,27 @@ def get_events(doc, method=None, page_length=10): print(results) for idx, event in enumerate(results): frappe.publish_realtime('import_google_calendar', dict(progress=idx+1, total=len(list(results))), user=frappe.session.user) - return_recurrence(event.get("recurrence")) - # if not frappe.db.exists("Event", {"subject": event.get("summary")}): - # frappe.get_doc({ - # "doctype": "Event", - # "subject": event.get("summary"), - # "description": event.get("description"), - # "start_on": event.get("start").get("dateTime"), - # "ends_on": event.get("end").get("dateTime"), - # "all_day": event.get("all_day"), - # "repeat_this_event": event.get("repeat_this_event"), - # "repeat_on": event.get("repeat_on"), - # "repeat_till": event.get("repeat_till"), - # "sunday": event.get("sunday"), - # "monday": event.get("monday"), - # "tuesday": event.get("tuesday"), - # "wednesday": event.get("wednesday"), - # "thursday": event.get("thursday"), - # "friday": event.get("friday"), - # "saturday": event.get("saturday"), - # "google_calendar_id": account.google_calendar_id, - # "google_event_id": event.get("id"), - # }).insert(ignore_permissions=True) -def insert_events(doc, method=None): + # If Google Calendar Event if confirmed, then create an Event + if event.get("status") == "confirmed" not frappe.db.exists("Event", {"google_calendar_id": account.google_calendar_id, "google_event_id": event.get("id")): + event = { + "doctype": "Event", + "subject": event.get("summary"), + "description": event.get("description"), + "google_calendar_id": account.google_calendar_id, + "google_event_id": event.get("id"), + } + event.update(get_repeat_on(event.get('recurrence')[0], event.get('start'), event.get('end'))) + + frappe.get_doc(event).insert(ignore_permissions=True) + + # If Google Calendar Event if cancelled, then delete the Event + if event.get("status") == "cancelled": + # Close the issue status once new PR is merged + # frappe.db.set_value("Event", {"google_calendar_id": account.google_calendar_id, "google_event_id": event.get("id")}, "status", "Closed") + pass + +def google_calendar_insert_events(doc, method=None): """ Insert Events with Google Calendar """ @@ -311,7 +299,7 @@ def insert_events(doc, method=None): except Exception: frappe.log_error(frappe.get_traceback(), _("Google Calendar Synchronization Error.")) -def update_events(doc, method=None): +def google_calendar_update_events(doc, method=None): """ Update Events with Google Calendar """ @@ -345,7 +333,7 @@ def update_events(doc, method=None): else: frappe.log_error(err.resp, "Google Calendar Synchronization Error.") -def delete_events(doc, method=None): +def google_calendar_delete_events(doc, method=None): """ Delete Events with Google Calendar """ @@ -357,6 +345,47 @@ def delete_events(doc, method=None): if err.resp.status in [410]: pass +def get_repeat_on(recurence, start, end): + repeat_on = { + "start_on": get_datetime(start.get("date")) if start.get("date") else parser.parse(start.get("dateTime")).utcnow(), + "ends_on" get_datetime(end.get("date")) if end.get("date") else parser.parse(end.get("dateTime")).utcnow(), + "all_day": 1 if start.get("date") else 0, + "repeat_this_event": 1 if recurence else 0, + "repeat_on": None, + "repeat_till": None, + "sunday": 0, + "monday": 0, + "tuesday": 0, + "wednesday": 0, + "thursday": 0, + "friday": 0, + "saturday": 0, + } + + if recurrence: + """ + recurrence is in the form ['RRULE:FREQ=WEEKLY;BYDAY=MO,TU,TH'] + has the frequency and then the days on which the event recurs + + Both have been mapped in a dict for easier mapping. + + After the first split on ';', + google_calendar_frequency = 'RRULE:FREQ=WEEKLY' + repeat_days = 'BYDAY=MO,TU,TH' which is further split on '=' and then on ',' which results in + repeat_days = ['MO', 'TU', 'TH'] + """ + google_calendar_frequency, repeat_days = recurence.split(";") + repeat_on["repeat_on"] = google_calendar_frequencies.get(google_calendar_frequency) + repeat_on["repeat_till"] = get_datetime(end.get("date")) if end.get("date") else parser.parse(end.get("dateTime")).utcnow() + + if repeat_days: + repeat_days = repeat_days.split("=")[1].split(",") + + for repeat_day in repeat_days: + repeat_on[google_calendar_days.get(repeat_day)] = 1 + + return repeat_on + def return_dates(doc): timezone = frappe.db.get_single_value("System Settings", "time_zone") if not doc.end_datetime: @@ -433,28 +462,28 @@ def parse_recurrence(recureence): - Recurrence Daily { 'kind': 'calendar#events', - 'etag': '"p32k8vl58mj7u60g"', + 'etag': '"etag"', 'summary': 'Test Calendar', 'updated': '2019-07-25T06:09:34.681Z', 'timeZone': 'Asia/Kolkata', 'accessRole': 'owner', 'defaultReminders': [], - 'nextSyncToken': 'CKiP1Ki0z-MCEKiP1Ki0z-MCGAU=', + 'nextSyncToken': 'token', 'items': [ { 'kind': 'calendar#event', - 'etag': '"3128069949362000"', - 'id': '6okmku7u8o4itknb7l1alu94ns', + 'etag': '"etag"', + 'id': 'id', 'status': 'confirmed', - 'htmlLink': 'https://www.google.com/calendar/event?eid=Nm9rbWt1N3U4bzRpdGtuYjdsMWFsdTk0bnNfMjAxOTA3MjdUMDYzMDAwWiBpd2Vibm90ZXMuY29tX2hqODFkZDA4aHJwaWNsbWY0anI3OWJzNG44QGc', + 'htmlLink': 'link', 'created': '2019-07-25T06:08:21.000Z', 'updated': '2019-07-25T06:09:34.681Z', 'summary': 'asdf', 'creator': { - 'email': 'himanshu@iwebnotes.com' + 'email': 'email' }, 'organizer': { - 'email': 'iwebnotes.com_hj81dd08hrpiclmf4jr79bs4n8@group.calendar.google.com', + 'email': 'email', 'displayName': 'Test Calendar', 'self': True }, @@ -467,7 +496,7 @@ def parse_recurrence(recureence): 'timeZone': 'Asia/Kolkata' }, 'recurrence': ['RRULE:FREQ=DAILY'], - 'iCalUID': '6okmku7u8o4itknb7l1alu94ns@google.com', + 'iCalUID': 'uid', 'sequence': 1, 'reminders': { 'useDefault': True @@ -475,31 +504,31 @@ def parse_recurrence(recureence): } ] } - - Recurrence Weekly + - Recurrence Weekly on a Day { 'kind': 'calendar#events', - 'etag': '"p320afgm8nv7u60g"', + 'etag': '"etag"', 'summary': 'Test Calendar', 'updated': '2019-07-25T06:59:54.288Z', 'timeZone': 'Asia/Kolkata', 'accessRole': 'owner', 'defaultReminders': [], - 'nextSyncToken': 'CICnwsi_z-MCEICnwsi_z-MCGAU=', + 'nextSyncToken': 'token', 'items': [ { 'kind': 'calendar#event', - 'etag': '"3128075988576000"', - 'id': '33n5br0htu51suqih4k8990181', + 'etag': '"etag"', + 'id': 'id', 'status': 'confirmed', - 'htmlLink': 'https://www.google.com/calendar/event?eid=MzNuNWJyMGh0dTUxc3VxaWg0azg5OTAxODFfMjAxOTA3MjVUMTIzMDAwWiBpd2Vibm90ZXMuY29tX25mOHAyaTFnazU5NDI2dTBsNzA4amU0OWVjQGc', + 'htmlLink': 'link', 'created': '2019-07-25T06:59:47.000Z', 'updated': '2019-07-25T06:59:54.288Z', 'summary': 'Event', 'creator': { - 'email': 'himanshu@iwebnotes.com' + 'email': 'email' }, 'organizer': { - 'email': 'iwebnotes.com_nf8p2i1gk59426u0l708je49ec@group.calendar.google.com', + 'email': 'email', 'displayName': 'Test Calendar', 'self': True }, @@ -512,7 +541,7 @@ def parse_recurrence(recureence): 'timeZone': 'Asia/Kolkata' }, 'recurrence': ['RRULE:FREQ=WEEKLY;BYDAY=TH'], - 'iCalUID': '33n5br0htu51suqih4k8990181@google.com', + 'iCalUID': 'uid', 'sequence': 1, 'reminders': { 'useDefault': True @@ -523,28 +552,28 @@ def parse_recurrence(recureence): - Recurrence Monthly on a Day { 'kind': 'calendar#events', - 'etag': '"p32oajev9ob7u60g"', + 'etag': '"etag"', 'summary': 'Test Calendar', 'updated': '2019-07-25T07:14:28.686Z', 'timeZone': 'Asia/Kolkata', 'accessRole': 'owner', 'defaultReminders': [], - 'nextSyncToken': 'CLCpu-nCz-MCELCpu-nCz-MCGAU=', + 'nextSyncToken': 'token', 'items': [ { 'kind': 'calendar#event', - 'etag': '"3128077737372000"', - 'id': '4up16101ptr37i5594asp0e692', + 'etag': '"etag"', + 'id': 'id', 'status': 'confirmed', - 'htmlLink': 'https://www.google.com/calendar/event?eid=NHVwMTYxMDFwdHIzN2k1NTk0YXNwMGU2OTJfMjAxOTA3MjVUMTMzMDAwWiBpd2Vibm90ZXMuY29tX25mOHAyaTFnazU5NDI2dTBsNzA4amU0OWVjQGc', + 'htmlLink': 'link', 'created': '2019-07-25T07:14:08.000Z', 'updated': '2019-07-25T07:14:28.686Z', 'summary': 'monthly 4 thusday', 'creator': { - 'email': 'himanshu@iwebnotes.com' + 'email': 'email' }, 'organizer': { - 'email': 'iwebnotes.com_nf8p2i1gk59426u0l708je49ec@group.calendar.google.com', + 'email': 'email', 'displayName': 'Test Calendar', 'self': True }, @@ -557,7 +586,50 @@ def parse_recurrence(recureence): 'timeZone': 'Asia/Kolkata' }, 'recurrence': ['RRULE:FREQ=MONTHLY;BYDAY=4TH'], - 'iCalUID': '4up16101ptr37i5594asp0e692@google.com', + 'iCalUID': 'uid', + 'sequence': 1, + 'reminders': { + 'useDefault': True + } + } + ] + } + - Daily Event: All Day (if an event is all day, then start and end has just date and not dateTime with timeZone) + { + 'kind': 'calendar#events', + 'etag': '"etag"', + 'summary': 'Test Calendar', + 'updated': '2019-07-27T07:20:50.494Z', + 'timeZone': 'Asia/Kolkata', + 'accessRole': 'owner', + 'defaultReminders': [], + 'nextSyncToken': 'tag', + 'items': [ + { + 'kind': 'calendar#event', + 'etag': '"etag"', + 'id': 'id', + 'status': 'confirmed', + 'htmlLink': 'link', + 'created': '2019-07-27T07:20:42.000Z', + 'updated': '2019-07-27T07:20:50.494Z', + 'summary': 'qwe', + 'creator': { + 'email': 'email' + }, + 'organizer': { + 'email': 'email', + 'displayName': 'Test Calendar', + 'self': True + }, + 'start': { + 'date': '2019-07-27' + }, + 'end': { + 'date': '2019-07-28' + }, + 'recurrence': ['RRULE:FREQ=DAILY'], + 'iCalUID': 'uid', 'sequence': 1, 'reminders': { 'useDefault': True From 5f59fc21dbba8c5cc02e29280d0d47afbbf5d9e1 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 27 Jul 2019 15:42:45 +0530 Subject: [PATCH 097/203] feat: only show open events --- frappe/desk/doctype/event/event.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/desk/doctype/event/event.py b/frappe/desk/doctype/event/event.py index b1306b432e..54d938716e 100644 --- a/frappe/desk/doctype/event/event.py +++ b/frappe/desk/doctype/event/event.py @@ -28,7 +28,7 @@ class Event(Document): if self.starts_on and self.ends_on: self.validate_from_to_dates("starts_on", "ends_on") - if self.repeat_on == "Daily" and getdate(self.starts_on) != getdate(self.ends_on): + if self.repeat_on == "Daily" and self.ends_on and getdate(self.starts_on) != getdate(self.ends_on): frappe.throw(_("Daily Events should finish on the Same Day.")) def on_update(self): @@ -194,6 +194,7 @@ def get_events(start, end, user=None, for_reminder=False, filters=None): AND `tabDocShare`.user=%(user)s ) ) + AND `tabEvent`.status='Open' ORDER BY `tabEvent`.starts_on""".format( filter_condition=get_filters_cond('Event', filters, []), reminder_condition="AND coalesce(`tabEvent`.send_reminder, 0)=1" if for_reminder else "" From d07935c50332df16cf08884b1cd724a413e4777f Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 27 Jul 2019 16:02:37 +0530 Subject: [PATCH 098/203] patch: optimize patch --- .../patches/v12_0/rename_events_repeat_on.py | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/frappe/patches/v12_0/rename_events_repeat_on.py b/frappe/patches/v12_0/rename_events_repeat_on.py index 81ba620329..b314ae663e 100644 --- a/frappe/patches/v12_0/rename_events_repeat_on.py +++ b/frappe/patches/v12_0/rename_events_repeat_on.py @@ -4,24 +4,15 @@ from frappe.utils import get_datetime def execute(): weekdays = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"] - daily_events = frappe.get_list("Event", filters={"repeat_this_event": 1, "repeat_on": "Every Day"}, fields=["name", "starts_on"]) weekly_events = frappe.get_list("Event", filters={"repeat_this_event": 1, "repeat_on": "Every Week"}, fields=["name", "starts_on"]) - monthly_events = frappe.get_list("Event", filters={"repeat_this_event": 1, "repeat_on": "Every Month"}, fields=["name", "starts_on"]) - yearly_events = frappe.get_list("Event", filters={"repeat_this_event": 1, "repeat_on": "Every Year"}, fields=["name", "starts_on"]) - frappe.reload_doc("desk", "doctype", "event") - for daily_event in daily_events: - # Initially Daily Events had option to choose days, but now Weekly does, so just changing from Daily -> Weekly does the job - frappe.db.set_value("Event", daily_event.name, "repeat_on", "Weekly") + # Initially Daily Events had option to choose days, but now Weekly does, so just changing from Daily -> Weekly does the job + frappe.db.sql("""UPDATE `tabEvent` SET `tabEvent`.repeat_on='Weekly' WHERE `tabEvent`.repeat_on='Every Day'""") + frappe.db.sql("""UPDATE `tabEvent` SET `tabEvent`.repeat_on='Weekly' WHERE `tabEvent`.repeat_on='Every Week'""") + frappe.db.sql("""UPDATE `tabEvent` SET `tabEvent`.repeat_on='Monthly' WHERE `tabEvent`.repeat_on='Every Month'""") + frappe.db.sql("""UPDATE `tabEvent` SET `tabEvent`.repeat_on='Yearly' WHERE `tabEvent`.repeat_on='Every Year'""") for weekly_event in weekly_events: # Set WeekDay based on the starts_on so that event can repeat Weekly - frappe.db.set_value("Event", weekly_event.name, "repeat_on", "Weekly") - frappe.db.set_value("Event", weekly_event.name, weekdays[get_datetime(weekly_event.starts_on).weekday()], 1) - - for monthly_event in monthly_events: - frappe.db.set_value("Event", monthly_event.name, "repeat_on", "Monthly") - - for yearly_event in yearly_events: - frappe.db.set_value("Event", yearly_event.name, "repeat_on", "Yearly") + frappe.db.sql("""UPDATE `tabEvent` SET `tabEvent`.{0}=1 WHERE `tabEvent`.name='{1}'""".format(weekdays[get_datetime(weekly_event.starts_on).weekday()], weekly_event.name)) \ No newline at end of file From 3d9502f150a065733d0e292f6bdd4388a5a159d6 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sun, 28 Jul 2019 11:39:04 +0530 Subject: [PATCH 099/203] feat: recurrence parser and unparser --- frappe/desk/doctype/event/event.json | 8 +- .../google_calendar/google_calendar.py | 353 ++++++++++-------- 2 files changed, 202 insertions(+), 159 deletions(-) diff --git a/frappe/desk/doctype/event/event.json b/frappe/desk/doctype/event/event.json index 03375796c4..5d715bdf4d 100644 --- a/frappe/desk/doctype/event/event.json +++ b/frappe/desk/doctype/event/event.json @@ -36,7 +36,7 @@ "sb_00", "google_calendar_id", "cb_00", - "google_event_id" + "google_calendar_event_id" ], "fields": [ { @@ -230,15 +230,15 @@ "fieldtype": "Column Break" }, { - "fieldname": "google_event_id", + "fieldname": "google_calendar_event_id", "fieldtype": "Data", - "label": "Google Event Id", + "label": "Google Calendar Event Id", "read_only": 1 } ], "icon": "fa fa-calendar", "idx": 1, - "modified": "2019-07-27 12:39:19.246328", + "modified": "2019-07-28 01:36:01.689757", "modified_by": "Administrator", "module": "Desk", "name": "Event", diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index b4e4f03db2..499e7dde36 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -8,12 +8,14 @@ import requests import googleapiclient.discovery import google.oauth2.credentials import time +import uuid from frappe import _ from frappe.model.document import Document from frappe.utils import get_request_site_address from googleapiclient.errors import HttpError -from frappe.utils import add_days, add_years, get_datetime +from frappe.utils import add_days, add_years, get_datetime, get_weekdays, now_datetime, add_to_date, get_time_zone from dateutil import parser +from datetime import timedelta SCOPES = "https://www.googleapis.com/auth/calendar/v3" @@ -26,7 +28,7 @@ google_calendar_frequencies = { google_calendar_days = { "MO": "monday", - "TU": "tuesday" + "TU": "tuesday", "WE": "wednesday", "TH": "thursday", "FR": "friday", @@ -35,15 +37,15 @@ google_calendar_days = { } framework_frequencies = { - "Every Day": "RRULE:FREQ=DAILY", - "Every Week": "RRULE:FREQ=WEEKLY", - "Every Month": "RRULE:FREQ=MONTHLY", - "Every Year": "RRULE:FREQ=YEARLY" + "Every Day": "RRULE:FREQ=DAILY;", + "Every Week": "RRULE:FREQ=WEEKLY;", + "Every Month": "RRULE:FREQ=MONTHLY;", + "Every Year": "RRULE:FREQ=YEARLY;" } framework_days = { "monday": "MO", - "tuesday": "TU" + "tuesday": "TU", "wednesday": "WE", "thursday": "TH", "friday": "FR", @@ -156,7 +158,7 @@ def sync(g_calendar=None): google_calendars = frappe.get_list("Google Calendar", filters=filters) for g in google_calendars: - google_calendar_get_events(frappe.get_doc("Google Calendar", g.name)) + google_calendar_get_events(g.name) def get_credentials(g_calendar): google_settings = frappe.get_doc("Google Settings") @@ -168,7 +170,7 @@ def get_credentials(g_calendar): "token_uri": "https://www.googleapis.com/oauth2/v4/token", "client_id": google_settings.client_id, "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), - "scopes":"https://www.googleapis.com/auth/calendar" + "scopes": SCOPES } credentials = google.oauth2.credentials.Credentials(**credentials_dict) @@ -209,30 +211,26 @@ def check_remote_calendar(account, google_calendar): frappe.log_error(frappe.get_traceback(), _("Google Calendar Synchronization Error.")) -def google_calendar_get_events(doc, method=None, page_length=10): - """ - Sync Events with Google Calendar - """ - if not doc.pull_from_google_calendar: +def google_calendar_get_events(g_calendar, method=None, page_length=10): + # Get Events from Google Calendar + google_calendar, account = get_credentials({"name": g_calendar}) + + if not account.pull_from_google_calendar: return - google_calendar, account = get_credentials(doc.name) - page_token = None results = [] - - print("1") while True: try: # API Response listed at EOF events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, singleEvents=False, showDeleted=True, syncToken=account.next_sync_token or None).execute() - print("2") + print("try") print(events) except HttpError as err: if err.resp.status in [404, 410]: events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, singleEvents=False, showDeleted=True, timeMin=add_years(None, -1).strftime("%Y-%m-%dT%H:%M:%SZ")).execute() - print("3") + print("except") print(events) else: frappe.log_error(err.resp, "Google Calendar Events Fetch Error.") @@ -246,24 +244,57 @@ def google_calendar_get_events(doc, method=None, page_length=10): frappe.db.commit() break - print(results) + # results = [ + # { + # 'kind': 'calendar#event', + # 'etag': '"etag"', + # 'id': 'id', + # 'status': 'confirmed', + # 'htmlLink': 'link', + # 'created': '2019-07-25T06:08:21.000Z', + # 'updated': '2019-07-25T06:09:34.681Z', + # 'summary': 'asdf', + # 'creator': { + # 'email': 'email' + # }, + # 'organizer': { + # 'email': 'email', + # 'displayName': 'Test Calendar', + # 'self': True + # }, + # 'start': { + # 'dateTime': '2019-07-27T12:00:00+05:30', + # 'timeZone': 'Asia/Kolkata' + # }, + # 'end': { + # 'dateTime': '2019-07-27T13:00:00+05:30', + # 'timeZone': 'Asia/Kolkata' + # }, + # 'recurrence': ['RRULE:FREQ=WEEKLY;BYDAY=SU,MO,WE,SA'], + # 'iCalUID': 'uid', + # 'sequence': 1, + # 'reminders': { + # 'useDefault': True + # } + # } + # ] for idx, event in enumerate(results): frappe.publish_realtime('import_google_calendar', dict(progress=idx+1, total=len(list(results))), user=frappe.session.user) # If Google Calendar Event if confirmed, then create an Event - if event.get("status") == "confirmed" not frappe.db.exists("Event", {"google_calendar_id": account.google_calendar_id, "google_event_id": event.get("id")): - event = { + if event.get("status") == "confirmed" and not frappe.db.exists("Event", {"google_calendar_id": account.google_calendar_id, "google_event_id": event.get("id")}): + calendar_event = { "doctype": "Event", "subject": event.get("summary"), "description": event.get("description"), "google_calendar_id": account.google_calendar_id, "google_event_id": event.get("id"), } - event.update(get_repeat_on(event.get('recurrence')[0], event.get('start'), event.get('end'))) + calendar_event.update(google_calendar_to_repeat_on(recurrence=event.get('recurrence')[0], start=event.get('start'), end=event.get('end'))) + print(calendar_event) + # frappe.get_doc(event).insert(ignore_permissions=True) - frappe.get_doc(event).insert(ignore_permissions=True) - - # If Google Calendar Event if cancelled, then delete the Event + # If anysynced Google Calendar Event is cancelled, then close the Event if event.get("status") == "cancelled": # Close the issue status once new PR is merged # frappe.db.set_value("Event", {"google_calendar_id": account.google_calendar_id, "google_event_id": event.get("id")}, "status", "Closed") @@ -271,86 +302,85 @@ def google_calendar_get_events(doc, method=None, page_length=10): def google_calendar_insert_events(doc, method=None): """ - Insert Events with Google Calendar + Insert Events to Google Calendar + UUID algorithm used minimize the risk of id collisions such as one described in RFC4122. + https://developers.google.com/calendar/v3/reference/events/insert """ - if not doc.push_to_google_calendar: + if not frappe.db.exists("Google Calendar", {"user": frappe.session.user}): return - google_calendar, account = get_credentials(doc.name) + google_calendar, account = get_credentials({"user": frappe.session.user}) + + if not account.push_to_google_calendar: + return event = { "summary": doc.summary, - "description": doc.description + "description": doc.description, + "id": str(uuid.uuid5(uuid.NAMESPACE_DNS, doc.name)) } + event.update(google_calendar_format_date(get_datetime(doc.starts_on), get_datetime(doc.ends_on))) - dates = return_dates(doc) - event.update(dates) - - if migration_id: - event.update({"id": doc.name}) - - if doc.repeat_this_event != 0: - recurrence = return_recurrence(doc) - if recurrence: - event.update({"recurrence": ["RRULE:" + str(recurrence)]}) + if doc.repeat_on: + event.update({"recurrence": unparse_recurrence(doc)}) try: - remote_event = google_calendar.events().insert(calendarId=account.google_calendar_id, body=event).execute() + google_calendar.events().insert(calendarId=account.google_calendar_id, body=event).execute() + doc.google_calendar_id = account.google_calendar_id + doc.google_calendar_event_id = event.get("id") + doc.save(ignore_permissions=True) except Exception: - frappe.log_error(frappe.get_traceback(), _("Google Calendar Synchronization Error.")) + frappe.log_error(frappe.get_traceback(), _("Google Calendar - Could not insert event in Google Calendar.")) def google_calendar_update_events(doc, method=None): """ Update Events with Google Calendar """ - google_calendar, account = get_credentials(doc.name) + if not frappe.db.exists("Google Calendar", {"user": frappe.session.user}): + return + + google_calendar, account = get_credentials({"user": frappe.session.user}) + + event = google_calendar.events().get(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id).execute() + event["summary"] = doc.summary + event["description"] = doc.description + event["recurrence"] = unparse_recurrence(doc) + event.update(google_calendar_format_date(get_datetime(doc.starts_on), get_datetime(doc.ends_on))) + + if doc.event_type == "Cancelled" or doc.status == "Closed": + event["status"] = "cancelled" try: - event = google_calendar.events().get(calendarId=account.google_calendar_id, eventId=doc.name).execute() - event = { - "summary": doc.summary, - "description": doc.description - } - - if doc.event_type == "Cancel": - event.update({"status": "cancelled"}) - - dates = return_dates(doc) - event.update(dates) - - if doc.repeat_this_event != 0: - recurrence = return_recurrence(doc) - if recurrence: - event.update({"recurrence": ["RRULE:" + str(recurrence)]}) - - try: - updated_event = google_calendar.events().update(calendarId=account.google_calendar_id, eventId=doc.name, body=event).execute() - except Exception as e: - frappe.log_error(e, "Google Calendar Synchronization Error.") - except HttpError as err: - if err.resp.status in [404]: - pass - else: - frappe.log_error(err.resp, "Google Calendar Synchronization Error.") + google_calendar.events().update(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id, body=event).execute() + except Exception as e: + frappe.log_error(e, "Google Calendar - Could not update event in Google Calendar.") def google_calendar_delete_events(doc, method=None): """ Delete Events with Google Calendar """ - google_calendar, account = get_credentials(doc.name) + if not frappe.db.exists("Google Calendar", {"user": frappe.session.user}): + return + + google_calendar, account = get_credentials({"user": frappe.session.user}) try: google_calendar.events().delete(calendarId=account.google_calendar_id, eventId=doc.name).execute() - except HttpError as err: - if err.resp.status in [410]: - pass + except Exception as e + frappe.log_error(e, "Google Calendar - Could not delete event from Google Calendar.") -def get_repeat_on(recurence, start, end): +def google_calendar_to_repeat_on(start, end, recurrence=None): + """ + recurrence is in the form ['RRULE:FREQ=WEEKLY;BYDAY=MO,TU,TH'] + has the frequency and then the days on which the event recurs + + Both have been mapped in a dict for easier mapping. + """ repeat_on = { - "start_on": get_datetime(start.get("date")) if start.get("date") else parser.parse(start.get("dateTime")).utcnow(), - "ends_on" get_datetime(end.get("date")) if end.get("date") else parser.parse(end.get("dateTime")).utcnow(), + "starts_on": get_datetime(start.get("date")) if start.get("date") else parser.parse(start.get("dateTime")).utcnow(), + "ends_on": get_datetime(end.get("date")) if end.get("date") else parser.parse(end.get("dateTime")).utcnow(), "all_day": 1 if start.get("date") else 0, - "repeat_this_event": 1 if recurence else 0, + "repeat_this_event": 1 if recurrence else 0, "repeat_on": None, "repeat_till": None, "sunday": 0, @@ -362,103 +392,116 @@ def get_repeat_on(recurence, start, end): "saturday": 0, } + # recurrence rule "RRULE:FREQ=WEEKLY;BYDAY=MO,TU,TH" if recurrence: - """ - recurrence is in the form ['RRULE:FREQ=WEEKLY;BYDAY=MO,TU,TH'] - has the frequency and then the days on which the event recurs + # google_calendar_frequency = RRULE:FREQ=WEEKLY, repeat_days = BYDAY=MO,TU,TH + google_calendar_frequency = get_indexed_value(recurrence, 0) + repeat_days = get_indexed_value(recurrence, 1) - Both have been mapped in a dict for easier mapping. - - After the first split on ';', - google_calendar_frequency = 'RRULE:FREQ=WEEKLY' - repeat_days = 'BYDAY=MO,TU,TH' which is further split on '=' and then on ',' which results in - repeat_days = ['MO', 'TU', 'TH'] - """ - google_calendar_frequency, repeat_days = recurence.split(";") repeat_on["repeat_on"] = google_calendar_frequencies.get(google_calendar_frequency) repeat_on["repeat_till"] = get_datetime(end.get("date")) if end.get("date") else parser.parse(end.get("dateTime")).utcnow() - if repeat_days: + if repeat_days and repeat_on["repeat_on"] == "Every Week": repeat_days = repeat_days.split("=")[1].split(",") - for repeat_day in repeat_days: - repeat_on[google_calendar_days.get(repeat_day)] = 1 + repeat_on[google_calendar_days[repeat_day]] = 1 + + if repeat_days and repeat_on["repeat_on"] == "Every Month": + repeat_days = repeat_days.split("=")[1] + for num in ["1", "2", "3", "4", "5"]: + repeat_day_number = num if num in repeat_days else None + + for day in ["MO","TU","WE","TH","FR","SA","SU"]: + repeat_day_name = google_calendar_days.get(day) if day in repeat_days else None + + repeat_on["starts_on"] = parse_recurrence(int(repeat_day_number), google_calendar_days.get(repeat_day_name)) + repeat_on["ends_on"] = None return repeat_on -def return_dates(doc): - timezone = frappe.db.get_single_value("System Settings", "time_zone") - if not doc.end_datetime: - doc.end_datetime = doc.start_datetime - if doc.all_day == 1: - return { - "start": { - "date": doc.start_datetime.date().isoformat(), - "timeZone": timezone, +def google_calendar_format_date(all_day, starts_on, ends_on=None): + if not ends_on: + ends_on = ends_on + timedelta(minutes=5) + + date_format = { + "start": { + "dateTime": starts_on.isoformat(), + "timeZone": get_time_zone(), }, - "end": { - "date": add_days(doc.end_datetime.date(), 1).isoformat(), - "timeZone": timezone, - } - } - else: - return { - "start": { - "dateTime": doc.start_datetime.isoformat(), - "timeZone": timezone, - }, - "end": { - "dateTime": doc.end_datetime.isoformat(), - "timeZone": timezone, - } + "end": { + "dateTime": ends_on.isoformat(), + "timeZone": get_time_zone(), } + } -def return_recurrence_for_google_calendar(recurrence): - if not e.repeat_till: - end_date = datetime.combine(e.repeat_till, datetime.min.time()).strftime("UNTIL=%Y%m%dT%H%M%SZ") - else: - end_date = None + if all_day: + # If all_day event, Google Calendar takes date as a parameter and not dateTime + date_format["start"].pop("dateTime") + date_format["end"].pop("dateTime") - day = [] - if e.repeat_on == "Every Day": - if e.monday == 1: - day.append("MO") - if e.tuesday == 1: - day.append("TU") - if e.wednesday == 1: - day.append("WE") - if e.thursday == 1: - day.append("TH") - if e.friday == 1: - day.append("FR") - if e.saturday == 1: - day.append("SA") - if e.sunday == 1: - day.append("SU") + date_format["start"].update({"date": starts_on.date().isoformat()}) + date_format["end"].update({"date": ends_on.date().isoformat()}) - day = "BYDAY=" + ",".join(str(d) for d in day) - frequency = "FREQ=WEEKLY" + return date_format - elif e.repeat_on == "Every Week": - frequency = "FREQ=WEEKLY" - elif e.repeat_on == "Every Month": - frequency = "FREQ=MONTHLY;BYDAY=SU,MO,TU,WE,TH,FR,SA;BYSETPOS=-1" - end_date = datetime.combine(add_days(e.repeat_till, 1), datetime.min.time()).strftime("UNTIL=%Y%m%dT%H%M%SZ") - elif e.repeat_on == "Every Year": - frequency = "FREQ=YEARLY" - else: +def parse_recurrence(repeat_day_number, repeat_day_name): + # Returns (repeat_on) exact date for combination eg 4TH viz. 4th thursday of a month + weekdays = get_weekdays() + current_date = now_datetime() + isset_day_name, isset_day_number = False, False + + # Set the proper day ie if recurrence is 4TH, then align the day to Thursday + while not isset_day_name: + isset_day_name = True if weekdays[current_date.weekday()].lower() == repeat_day_name else False + current_date = add_days(current_date, 1) if not isset_day_name else current_date + + # One the day is set ir Thursday, now set the week number + while not isset_day_number: + isset_day_number = True if get_week_number(current_date) == repeat_day_number else False + current_date = add_to_date(current_date, weeks=1) if not isset_day_number else current_date + + return current_date + +def unparse_recurrence(doc): + # Returns recurrence in Google Calendar format + recurrence = framework_frequencies.get(doc.repeat_on) + weekdays = get_weekdays() + + if doc.repeat_on == "Every Week": + repeat_days = [framework_days.get(day.lower()) for day in weekdays if doc.get(day.lower())] + recurrence = recurrence + "BYDAY=" + ",".join(repeat_days) + elif doc.repeat_on == "Every Month": + week_number = str(get_week_number(doc.starts_on)) + week_day = weekdays[get_datetime(doc.starts_on).weekday()].lower() + recurrence = recurrence + "BYDAY=" + week_number + framework_days.get(week_day) + + return [recurrence] + +def get_week_number(dt): + """ + Returns the week number of the month for the specified date. + https://stackoverflow.com/questions/3806473/python-week-number-of-the-month/16804556 + """ + from math import ceil + first_day = dt.replace(day=1) + + dom = dt.day + adjusted_dom = dom + first_day.weekday() + + return int(ceil(adjusted_dom/7.0)) + +def get_indexed_value(d, index): + # Returns value from string based on index + if not d: return None - wst = "WKST=SU" - elements = [frequency, end_date, wst, day] - - return ";".join(str(e) for e in elements if e is not None and not not e) - -def parse_recurrence(recureence): - pass - + try: + return d.split(";")[index] + except IndexError: + return None """ + API Responses - Recurrence Daily { 'kind': 'calendar#events', From dc74efd28dc3a7b01ae9bb0280131bac646a2913 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sun, 28 Jul 2019 13:49:54 +0530 Subject: [PATCH 100/203] patch: patch and codacy fixes --- .../connectors/calendar_connector.py | 250 ------------------ .../google_calendar/google_calendar.py | 48 +--- .../patches/v12_0/remove_gcalendar_gmaps.py | 10 + 3 files changed, 17 insertions(+), 291 deletions(-) delete mode 100644 frappe/data_migration/doctype/data_migration_connector/connectors/calendar_connector.py create mode 100644 frappe/patches/v12_0/remove_gcalendar_gmaps.py diff --git a/frappe/data_migration/doctype/data_migration_connector/connectors/calendar_connector.py b/frappe/data_migration/doctype/data_migration_connector/connectors/calendar_connector.py deleted file mode 100644 index 578ac0fc37..0000000000 --- a/frappe/data_migration/doctype/data_migration_connector/connectors/calendar_connector.py +++ /dev/null @@ -1,250 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.data_migration.doctype.data_migration_connector.connectors.base import BaseConnection -import googleapiclient.discovery -import google.oauth2.credentials -from googleapiclient.errors import HttpError -import time -from datetime import datetime -from frappe.utils import add_days, add_years -from frappe.desk.doctype.event.event import has_permission - -class CalendarConnector(BaseConnection): - def __init__(self, connector): - self.connector = connector - settings = frappe.get_doc("GCalendar Settings", None) - - self.account = frappe.get_doc("GCalendar Account", connector.username) - - self.credentials_dict = { - 'token': self.account.get_password(fieldname='session_token', raise_exception=False), - 'refresh_token': self.account.get_password(fieldname='refresh_token', raise_exception=False), - 'token_uri': 'https://www.googleapis.com/oauth2/v4/token', - 'client_id': settings.client_id, - 'client_secret': settings.get_password(fieldname='client_secret', raise_exception=False), - 'scopes':'https://www.googleapis.com/auth/calendar' - } - - self.name_field = 'id' - - self.credentials = google.oauth2.credentials.Credentials(**self.credentials_dict) - self.gcalendar = googleapiclient.discovery.build('calendar', 'v3', credentials=self.credentials) - - self.check_remote_calendar() - - def check_remote_calendar(self): - def _create_calendar(): - timezone = frappe.db.get_value("System Settings", None, "time_zone") - calendar = { - 'summary': self.account.calendar_name, - 'timeZone': timezone - } - try: - created_calendar = self.gcalendar.calendars().insert(body=calendar).execute() - frappe.db.set_value("GCalendar Account", self.account.name, "gcalendar_id", created_calendar["id"]) - except Exception: - frappe.log_error(frappe.get_traceback()) - try: - if self.account.gcalendar_id is not None: - try: - self.gcalendar.calendars().get(calendarId=self.account.gcalendar_id).execute() - except Exception: - frappe.log_error(frappe.get_traceback()) - else: - _create_calendar() - except HttpError as err: - if err.resp.status in [403, 500, 503]: - time.sleep(5) - elif err.resp.status in [404]: - _create_calendar() - else: raise - - - def get(self, remote_objectname, fields=None, filters=None, start=0, page_length=10): - return self.get_events(remote_objectname, filters, page_length) - - def insert(self, doctype, doc): - if doctype == 'Events': - d = frappe.get_doc("Event", doc["name"]) - if has_permission(d, self.account.name): - try: - doctype = "Event" - e = self.insert_events(doctype, doc) - return e - except Exception: - frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error") - - - def update(self, doctype, doc, migration_id): - if doctype == 'Events': - d = frappe.get_doc("Event", doc["name"]) - if has_permission(d, self.account.name): - if migration_id is not None: - try: - doctype = "Event" - return self.update_events(doctype, doc, migration_id) - except Exception: - frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error") - - def delete(self, doctype, migration_id): - if doctype == 'Events': - try: - return self.delete_events(migration_id) - except Exception: - frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error") - - def get_events(self, remote_objectname, filters, page_length): - page_token = None - results = [] - events = {"items": []} - while True: - try: - events = self.gcalendar.events().list(calendarId=self.account.gcalendar_id, maxResults=page_length, - singleEvents=False, showDeleted=True, syncToken=self.account.next_sync_token or None).execute() - except HttpError as err: - if err.resp.status in [410]: - events = self.gcalendar.events().list(calendarId=self.account.gcalendar_id, maxResults=page_length, - singleEvents=False, showDeleted=True, timeMin=add_years(None, -1).strftime('%Y-%m-%dT%H:%M:%SZ')).execute() - else: - frappe.log_error(err.resp, "GCalendar Events Fetch Error") - for event in events['items']: - event.update({'account': self.account.name}) - event.update({'calendar_tz': events['timeZone']}) - results.append(event) - - page_token = events.get('nextPageToken') - if not page_token: - if events.get('nextSyncToken'): - frappe.db.set_value("GCalendar Account", self.connector.username, "next_sync_token", events.get('nextSyncToken')) - break - return list(results) - - def insert_events(self, doctype, doc, migration_id=None): - event = { - 'summary': doc.summary, - 'description': doc.description - } - - dates = self.return_dates(doc) - event.update(dates) - - if migration_id: - event.update({"id": migration_id}) - - if doc.repeat_this_event != 0: - recurrence = self.return_recurrence(doctype, doc) - if not not recurrence: - event.update({"recurrence": ["RRULE:" + str(recurrence)]}) - - try: - remote_event = self.gcalendar.events().insert(calendarId=self.account.gcalendar_id, body=event).execute() - return {self.name_field: remote_event["id"]} - except Exception: - frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error") - - def update_events(self, doctype, doc, migration_id): - try: - event = self.gcalendar.events().get(calendarId=self.account.gcalendar_id, eventId=migration_id).execute() - event = { - 'summary': doc.summary, - 'description': doc.description - } - - if doc.event_type == "Cancel": - event.update({"status": "cancelled"}) - - dates = self.return_dates(doc) - event.update(dates) - - if doc.repeat_this_event != 0: - recurrence = self.return_recurrence(doctype, doc) - if recurrence: - event.update({"recurrence": ["RRULE:" + str(recurrence)]}) - - try: - updated_event = self.gcalendar.events().update(calendarId=self.account.gcalendar_id, eventId=migration_id, body=event).execute() - return {self.name_field: updated_event["id"]} - except Exception as e: - frappe.log_error(e, "GCalendar Synchronization Error") - except HttpError as err: - if err.resp.status in [404]: - self.insert_events(doctype, doc, migration_id) - else: - frappe.log_error(err.resp, "GCalendar Synchronization Error") - - def delete_events(self, migration_id): - try: - self.gcalendar.events().delete(calendarId=self.account.gcalendar_id, eventId=migration_id).execute() - except HttpError as err: - if err.resp.status in [410]: - pass - - def return_dates(self, doc): - timezone = frappe.db.get_value("System Settings", None, "time_zone") - if doc.end_datetime is None: - doc.end_datetime = doc.start_datetime - if doc.all_day == 1: - return { - 'start': { - 'date': doc.start_datetime.date().isoformat(), - 'timeZone': timezone, - }, - 'end': { - 'date': add_days(doc.end_datetime.date(), 1).isoformat(), - 'timeZone': timezone, - } - } - else: - return { - 'start': { - 'dateTime': doc.start_datetime.isoformat(), - 'timeZone': timezone, - }, - 'end': { - 'dateTime': doc.end_datetime.isoformat(), - 'timeZone': timezone, - } - } - - def return_recurrence(self, doctype, doc): - e = frappe.get_doc(doctype, doc.name) - if e.repeat_till is not None: - end_date = datetime.combine(e.repeat_till, datetime.min.time()).strftime('UNTIL=%Y%m%dT%H%M%SZ') - else: - end_date = None - - day = [] - if e.repeat_on == "Every Day": - if e.monday == 1: - day.append("MO") - if e.tuesday == 1: - day.append("TU") - if e.wednesday == 1: - day.append("WE") - if e.thursday == 1: - day.append("TH") - if e.friday == 1: - day.append("FR") - if e.saturday == 1: - day.append("SA") - if e.sunday == 1: - day.append("SU") - - day = "BYDAY=" + ",".join(str(d) for d in day) - frequency = "FREQ=WEEKLY" - - elif e.repeat_on == "Every Week": - frequency = "FREQ=WEEKLY" - elif e.repeat_on == "Every Month": - frequency = "FREQ=MONTHLY;BYDAY=SU,MO,TU,WE,TH,FR,SA;BYSETPOS=-1" - end_date = datetime.combine(add_days(e.repeat_till, 1), datetime.min.time()).strftime('UNTIL=%Y%m%dT%H%M%SZ') - elif e.repeat_on == "Every Year": - frequency = "FREQ=YEARLY" - else: - return None - - wst = "WKST=SU" - - elements = [frequency, end_date, wst, day] - - return ";".join(str(e) for e in elements if e is not None and not not e) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 499e7dde36..929ff70155 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -219,6 +219,7 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): return results = [] + while True: try: # API Response listed at EOF @@ -244,42 +245,8 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): frappe.db.commit() break - # results = [ - # { - # 'kind': 'calendar#event', - # 'etag': '"etag"', - # 'id': 'id', - # 'status': 'confirmed', - # 'htmlLink': 'link', - # 'created': '2019-07-25T06:08:21.000Z', - # 'updated': '2019-07-25T06:09:34.681Z', - # 'summary': 'asdf', - # 'creator': { - # 'email': 'email' - # }, - # 'organizer': { - # 'email': 'email', - # 'displayName': 'Test Calendar', - # 'self': True - # }, - # 'start': { - # 'dateTime': '2019-07-27T12:00:00+05:30', - # 'timeZone': 'Asia/Kolkata' - # }, - # 'end': { - # 'dateTime': '2019-07-27T13:00:00+05:30', - # 'timeZone': 'Asia/Kolkata' - # }, - # 'recurrence': ['RRULE:FREQ=WEEKLY;BYDAY=SU,MO,WE,SA'], - # 'iCalUID': 'uid', - # 'sequence': 1, - # 'reminders': { - # 'useDefault': True - # } - # } - # ] for idx, event in enumerate(results): - frappe.publish_realtime('import_google_calendar', dict(progress=idx+1, total=len(list(results))), user=frappe.session.user) + frappe.publish_realtime('import_google_calendar', dict(progress=idx+1, total=len(results)), user=frappe.session.user) # If Google Calendar Event if confirmed, then create an Event if event.get("status") == "confirmed" and not frappe.db.exists("Event", {"google_calendar_id": account.google_calendar_id, "google_event_id": event.get("id")}): @@ -294,7 +261,7 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): print(calendar_event) # frappe.get_doc(event).insert(ignore_permissions=True) - # If anysynced Google Calendar Event is cancelled, then close the Event + # If any synced Google Calendar Event is cancelled, then close the Event if event.get("status") == "cancelled": # Close the issue status once new PR is merged # frappe.db.set_value("Event", {"google_calendar_id": account.google_calendar_id, "google_event_id": event.get("id")}, "status", "Closed") @@ -356,17 +323,16 @@ def google_calendar_update_events(doc, method=None): frappe.log_error(e, "Google Calendar - Could not update event in Google Calendar.") def google_calendar_delete_events(doc, method=None): - """ - Delete Events with Google Calendar - """ + # Delete Events from Google Calendar + if not frappe.db.exists("Google Calendar", {"user": frappe.session.user}): return google_calendar, account = get_credentials({"user": frappe.session.user}) try: - google_calendar.events().delete(calendarId=account.google_calendar_id, eventId=doc.name).execute() - except Exception as e + google_calendar.events().delete(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id).execute() + except Exception as e: frappe.log_error(e, "Google Calendar - Could not delete event from Google Calendar.") def google_calendar_to_repeat_on(start, end, recurrence=None): diff --git a/frappe/patches/v12_0/remove_gcalendar_gmaps.py b/frappe/patches/v12_0/remove_gcalendar_gmaps.py new file mode 100644 index 0000000000..84c400f6a8 --- /dev/null +++ b/frappe/patches/v12_0/remove_gcalendar_gmaps.py @@ -0,0 +1,10 @@ +import frappe + +def execute(): + ''' + Remove GCalendar and GCalendar Settings + Remove Google Maps Settings as its been merged with Delivery Trips + ''' + frappe.delete_doc_if_exists('DocType', 'GCalendar Account') + frappe.delete_doc_if_exists('DocType', 'GCalendar Settings') + frappe.delete_doc_if_exists('DocType', 'Google Maps Settings') From b21a27caa407ce8e20ebd96f1eafb6abcc362694 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sun, 28 Jul 2019 20:40:22 +0530 Subject: [PATCH 101/203] feat: add google calendar check in events --- frappe/desk/doctype/event/event.json | 12 +- frappe/hooks.py | 2 +- .../google_calendar/google_calendar.py | 208 ++++-------------- 3 files changed, 59 insertions(+), 163 deletions(-) diff --git a/frappe/desk/doctype/event/event.json b/frappe/desk/doctype/event/event.json index 5d715bdf4d..56dfabb513 100644 --- a/frappe/desk/doctype/event/event.json +++ b/frappe/desk/doctype/event/event.json @@ -16,6 +16,7 @@ "starts_on", "ends_on", "all_day", + "google_calendar_event", "section_break_13", "repeat_on", "repeat_till", @@ -214,7 +215,7 @@ }, { "collapsible": 1, - "depends_on": "eval:doc.google_calendar_id && doc.google_event_id", + "depends_on": "eval:doc.google_calendar_event", "fieldname": "sb_00", "fieldtype": "Section Break", "label": "Google Calendar" @@ -234,11 +235,18 @@ "fieldtype": "Data", "label": "Google Calendar Event Id", "read_only": 1 + }, + { + "default": "0", + "fieldname": "google_calendar_event", + "fieldtype": "Check", + "label": "Google Calendar Event", + "read_only": 1 } ], "icon": "fa fa-calendar", "idx": 1, - "modified": "2019-07-28 01:36:01.689757", + "modified": "2019-07-28 14:22:27.221143", "modified_by": "Administrator", "module": "Desk", "name": "Event", diff --git a/frappe/hooks.py b/frappe/hooks.py index b49e3148ac..f021c0815e 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -143,7 +143,7 @@ doc_events = { "validate": "frappe.email.doctype.email_group.email_group.restrict_email_group" }, "Event": { - "after_insert": "frappe.integrations.doctype.google_calendar.google_calendar.google_calendar_get_events", + "after_insert": "frappe.integrations.doctype.google_calendar.google_calendar.google_calendar_insert_events", "on_update": "frappe.integrations.doctype.google_calendar.google_calendar.google_calendar_update_events", "on_trash": "frappe.integrations.doctype.google_calendar.google_calendar.google_calendar_delete_events", } diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 929ff70155..6af1a78569 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -210,7 +210,6 @@ def check_remote_calendar(account, google_calendar): else: frappe.log_error(frappe.get_traceback(), _("Google Calendar Synchronization Error.")) - def google_calendar_get_events(g_calendar, method=None, page_length=10): # Get Events from Google Calendar google_calendar, account = get_credentials({"name": g_calendar}) @@ -258,7 +257,6 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): "google_event_id": event.get("id"), } calendar_event.update(google_calendar_to_repeat_on(recurrence=event.get('recurrence')[0], start=event.get('start'), end=event.get('end'))) - print(calendar_event) # frappe.get_doc(event).insert(ignore_permissions=True) # If any synced Google Calendar Event is cancelled, then close the Event @@ -282,9 +280,10 @@ def google_calendar_insert_events(doc, method=None): return event = { - "summary": doc.summary, + "summary": doc.subject, "description": doc.description, - "id": str(uuid.uuid5(uuid.NAMESPACE_DNS, doc.name)) + "id": str(uuid.uuid5(uuid.NAMESPACE_DNS, doc.name)), + "google_calendar_event": 1 } event.update(google_calendar_format_date(get_datetime(doc.starts_on), get_datetime(doc.ends_on))) @@ -292,17 +291,23 @@ def google_calendar_insert_events(doc, method=None): event.update({"recurrence": unparse_recurrence(doc)}) try: - google_calendar.events().insert(calendarId=account.google_calendar_id, body=event).execute() - doc.google_calendar_id = account.google_calendar_id - doc.google_calendar_event_id = event.get("id") - doc.save(ignore_permissions=True) - except Exception: + # google_calendar.events().insert(calendarId=account.google_calendar_id, body=event).execute() + # frappe.db.set_value("Event", doc.name, "google_calendar_event", 1, update_modified=False) + # frappe.db.set_value("Event", doc.name, "google_calendar_id", account.google_calendar_id, update_modified=False) + # frappe.db.set_value("Event", doc.name, "google_calendar_event_id", event.get("id"), update_modified=False) + except HttpError as e: frappe.log_error(frappe.get_traceback(), _("Google Calendar - Could not insert event in Google Calendar.")) def google_calendar_update_events(doc, method=None): - """ - Update Events with Google Calendar - """ + # Update Events with Google Calendar + + # Workaround to avoid triggering updation when Event is being inserted since + # creation and modified are same when inserting doc + print("doc.modified, doc.creation") + print(doc.modified, doc.creation) + if doc.modified == doc.creation: + return + if not frappe.db.exists("Google Calendar", {"user": frappe.session.user}): return @@ -374,20 +379,29 @@ def google_calendar_to_repeat_on(start, end, recurrence=None): if repeat_days and repeat_on["repeat_on"] == "Every Month": repeat_days = repeat_days.split("=")[1] + repeat_day_week_number, repeat_day_name = None, None + for num in ["1", "2", "3", "4", "5"]: - repeat_day_number = num if num in repeat_days else None + if num in repeat_days: + repeat_day_week_number = num + break for day in ["MO","TU","WE","TH","FR","SA","SU"]: - repeat_day_name = google_calendar_days.get(day) if day in repeat_days else None + if day in repeat_days: + repeat_day_name = google_calendar_days.get(day) + break - repeat_on["starts_on"] = parse_recurrence(int(repeat_day_number), google_calendar_days.get(repeat_day_name)) - repeat_on["ends_on"] = None + # Only Set starts_on for the event to repeat monthly + start_date = parse_recurrence(int(repeat_day_week_number), repeat_day_name) + repeat_on["starts_on"] = start_date + repeat_on["ends_on"] = add_to_date(minutes=5) + repeat_on["repeat_till"] = None return repeat_on def google_calendar_format_date(all_day, starts_on, ends_on=None): if not ends_on: - ends_on = ends_on + timedelta(minutes=5) + ends_on = starts_on + timedelta(minutes=10) date_format = { "start": { @@ -410,7 +424,7 @@ def google_calendar_format_date(all_day, starts_on, ends_on=None): return date_format -def parse_recurrence(repeat_day_number, repeat_day_name): +def parse_recurrence(repeat_day_week_number, repeat_day_name): # Returns (repeat_on) exact date for combination eg 4TH viz. 4th thursday of a month weekdays = get_weekdays() current_date = now_datetime() @@ -423,8 +437,11 @@ def parse_recurrence(repeat_day_number, repeat_day_name): # One the day is set ir Thursday, now set the week number while not isset_day_number: - isset_day_number = True if get_week_number(current_date) == repeat_day_number else False - current_date = add_to_date(current_date, weeks=1) if not isset_day_number else current_date + week_number = get_week_number(current_date) + isset_day_number = True if week_number == repeat_day_week_number else False + # check if current_date week number is greater or smaller than repeat_day week number + weeks = 1 if week_number < repeat_day_week_number else -1 + current_date = add_to_date(current_date, weeks=weeks) if not isset_day_number else current_date return current_date @@ -437,7 +454,7 @@ def unparse_recurrence(doc): repeat_days = [framework_days.get(day.lower()) for day in weekdays if doc.get(day.lower())] recurrence = recurrence + "BYDAY=" + ",".join(repeat_days) elif doc.repeat_on == "Every Month": - week_number = str(get_week_number(doc.starts_on)) + week_number = str(get_week_number(get_datetime(doc.starts_on))) week_day = weekdays[get_datetime(doc.starts_on).weekday()].lower() recurrence = recurrence + "BYDAY=" + week_number + framework_days.get(week_day) @@ -467,8 +484,7 @@ def get_indexed_value(d, index): return None """ - API Responses - - Recurrence Daily + API Response { 'kind': 'calendar#events', 'etag': '"etag"', @@ -483,7 +499,7 @@ def get_indexed_value(d, index): 'kind': 'calendar#event', 'etag': '"etag"', 'id': 'id', - 'status': 'confirmed', + 'status': 'confirmed' or 'cancelled', 'htmlLink': 'link', 'created': '2019-07-25T06:08:21.000Z', 'updated': '2019-07-25T06:09:34.681Z', @@ -497,147 +513,14 @@ def get_indexed_value(d, index): 'self': True }, 'start': { - 'dateTime': '2019-07-27T12:00:00+05:30', + 'dateTime': '2019-07-27T12:00:00+05:30', (if all day event the its 'date' instead of 'dateTime') 'timeZone': 'Asia/Kolkata' }, 'end': { - 'dateTime': '2019-07-27T13:00:00+05:30', + 'dateTime': '2019-07-27T13:00:00+05:30', (if all day event the its 'date' instead of 'dateTime') 'timeZone': 'Asia/Kolkata' }, - 'recurrence': ['RRULE:FREQ=DAILY'], - 'iCalUID': 'uid', - 'sequence': 1, - 'reminders': { - 'useDefault': True - } - } - ] - } - - Recurrence Weekly on a Day - { - 'kind': 'calendar#events', - 'etag': '"etag"', - 'summary': 'Test Calendar', - 'updated': '2019-07-25T06:59:54.288Z', - 'timeZone': 'Asia/Kolkata', - 'accessRole': 'owner', - 'defaultReminders': [], - 'nextSyncToken': 'token', - 'items': [ - { - 'kind': 'calendar#event', - 'etag': '"etag"', - 'id': 'id', - 'status': 'confirmed', - 'htmlLink': 'link', - 'created': '2019-07-25T06:59:47.000Z', - 'updated': '2019-07-25T06:59:54.288Z', - 'summary': 'Event', - 'creator': { - 'email': 'email' - }, - 'organizer': { - 'email': 'email', - 'displayName': 'Test Calendar', - 'self': True - }, - 'start': { - 'dateTime': '2019-07-25T18:00:00+05:30', - 'timeZone': 'Asia/Kolkata' - }, - 'end': { - 'dateTime': '2019-07-25T19:00:00+05:30', - 'timeZone': 'Asia/Kolkata' - }, - 'recurrence': ['RRULE:FREQ=WEEKLY;BYDAY=TH'], - 'iCalUID': 'uid', - 'sequence': 1, - 'reminders': { - 'useDefault': True - } - } - ] - } - - Recurrence Monthly on a Day - { - 'kind': 'calendar#events', - 'etag': '"etag"', - 'summary': 'Test Calendar', - 'updated': '2019-07-25T07:14:28.686Z', - 'timeZone': 'Asia/Kolkata', - 'accessRole': 'owner', - 'defaultReminders': [], - 'nextSyncToken': 'token', - 'items': [ - { - 'kind': 'calendar#event', - 'etag': '"etag"', - 'id': 'id', - 'status': 'confirmed', - 'htmlLink': 'link', - 'created': '2019-07-25T07:14:08.000Z', - 'updated': '2019-07-25T07:14:28.686Z', - 'summary': 'monthly 4 thusday', - 'creator': { - 'email': 'email' - }, - 'organizer': { - 'email': 'email', - 'displayName': 'Test Calendar', - 'self': True - }, - 'start': { - 'dateTime': '2019-07-25T19:00:00+05:30', - 'timeZone': 'Asia/Kolkata' - }, - 'end': { - 'dateTime': '2019-07-25T20:00:00+05:30', - 'timeZone': 'Asia/Kolkata' - }, - 'recurrence': ['RRULE:FREQ=MONTHLY;BYDAY=4TH'], - 'iCalUID': 'uid', - 'sequence': 1, - 'reminders': { - 'useDefault': True - } - } - ] - } - - Daily Event: All Day (if an event is all day, then start and end has just date and not dateTime with timeZone) - { - 'kind': 'calendar#events', - 'etag': '"etag"', - 'summary': 'Test Calendar', - 'updated': '2019-07-27T07:20:50.494Z', - 'timeZone': 'Asia/Kolkata', - 'accessRole': 'owner', - 'defaultReminders': [], - 'nextSyncToken': 'tag', - 'items': [ - { - 'kind': 'calendar#event', - 'etag': '"etag"', - 'id': 'id', - 'status': 'confirmed', - 'htmlLink': 'link', - 'created': '2019-07-27T07:20:42.000Z', - 'updated': '2019-07-27T07:20:50.494Z', - 'summary': 'qwe', - 'creator': { - 'email': 'email' - }, - 'organizer': { - 'email': 'email', - 'displayName': 'Test Calendar', - 'self': True - }, - 'start': { - 'date': '2019-07-27' - }, - 'end': { - 'date': '2019-07-28' - }, - 'recurrence': ['RRULE:FREQ=DAILY'], + 'recurrence': *recurrence, 'iCalUID': 'uid', 'sequence': 1, 'reminders': { @@ -646,4 +529,9 @@ def get_indexed_value(d, index): } ] } + *recurrence + - Daily Event: ['RRULE:FREQ=DAILY'] + - Weekly Event: ['RRULE:FREQ=WEEKLY;BYDAY=MO,TU,TH'] + - Monthly Event: ['RRULE:FREQ=MONTHLY;BYDAY=4TH'] + - Yearly Event: ['RRULE:FREQ=YEARLY;'] """ \ No newline at end of file From 1df0f7600dbef7cf50ddddf5bd489482a6b23471 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sun, 28 Jul 2019 21:26:53 +0530 Subject: [PATCH 102/203] fix: indentation --- frappe/integrations/doctype/google_calendar/google_calendar.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 6af1a78569..ee8f64a03a 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -291,6 +291,7 @@ def google_calendar_insert_events(doc, method=None): event.update({"recurrence": unparse_recurrence(doc)}) try: + pass # google_calendar.events().insert(calendarId=account.google_calendar_id, body=event).execute() # frappe.db.set_value("Event", doc.name, "google_calendar_event", 1, update_modified=False) # frappe.db.set_value("Event", doc.name, "google_calendar_id", account.google_calendar_id, update_modified=False) From dc16b1f40408b519c56338f0e7fba09f02bbaa14 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sun, 28 Jul 2019 22:39:41 +0530 Subject: [PATCH 103/203] fix: remove gcalendar settings and account --- .../doctype/gcalendar_account/__init__.py | 0 .../gcalendar_account/gcalendar_account.js | 19 - .../gcalendar_account/gcalendar_account.json | 533 ------------------ .../gcalendar_account/gcalendar_account.py | 30 - .../test_gcalendar_account.js | 23 - .../test_gcalendar_account.py | 20 - .../doctype/gcalendar_settings/__init__.py | 0 .../gcalendar_settings/gcalendar_settings.js | 7 - .../gcalendar_settings.json | 373 ------------ .../gcalendar_settings/gcalendar_settings.py | 121 ---- .../test_gcalendar_settings.js | 23 - .../test_gcalendar_settings.py | 9 - .../google_calendar/google_calendar.py | 8 +- 13 files changed, 4 insertions(+), 1162 deletions(-) delete mode 100644 frappe/integrations/doctype/gcalendar_account/__init__.py delete mode 100644 frappe/integrations/doctype/gcalendar_account/gcalendar_account.js delete mode 100644 frappe/integrations/doctype/gcalendar_account/gcalendar_account.json delete mode 100644 frappe/integrations/doctype/gcalendar_account/gcalendar_account.py delete mode 100644 frappe/integrations/doctype/gcalendar_account/test_gcalendar_account.js delete mode 100644 frappe/integrations/doctype/gcalendar_account/test_gcalendar_account.py delete mode 100644 frappe/integrations/doctype/gcalendar_settings/__init__.py delete mode 100644 frappe/integrations/doctype/gcalendar_settings/gcalendar_settings.js delete mode 100644 frappe/integrations/doctype/gcalendar_settings/gcalendar_settings.json delete mode 100644 frappe/integrations/doctype/gcalendar_settings/gcalendar_settings.py delete mode 100644 frappe/integrations/doctype/gcalendar_settings/test_gcalendar_settings.js delete mode 100644 frappe/integrations/doctype/gcalendar_settings/test_gcalendar_settings.py diff --git a/frappe/integrations/doctype/gcalendar_account/__init__.py b/frappe/integrations/doctype/gcalendar_account/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/integrations/doctype/gcalendar_account/gcalendar_account.js b/frappe/integrations/doctype/gcalendar_account/gcalendar_account.js deleted file mode 100644 index d8ad7d46ad..0000000000 --- a/frappe/integrations/doctype/gcalendar_account/gcalendar_account.js +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2018, DOKOS and contributors -// For license information, please see license.txt - -frappe.ui.form.on('GCalendar Account', { - allow_google_access: function(frm) { - frappe.call({ - method: "frappe.integrations.doctype.gcalendar_settings.gcalendar_settings.google_callback", - args: { - 'account': frm.doc.name - }, - callback: function(r) { - if(!r.exc) { - frm.save(); - window.open(r.message.url); - } - } - }); - } -}); diff --git a/frappe/integrations/doctype/gcalendar_account/gcalendar_account.json b/frappe/integrations/doctype/gcalendar_account/gcalendar_account.json deleted file mode 100644 index d531d32862..0000000000 --- a/frappe/integrations/doctype/gcalendar_account/gcalendar_account.json +++ /dev/null @@ -1,533 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:user", - "beta": 0, - "creation": "2018-02-13 09:42:24.068671", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fieldname": "enabled", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Enabled", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "user", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "User", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 1 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "The name that will appear in Google Calendar", - "fieldname": "calendar_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Calendar Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": "", - "columns": 0, - "depends_on": "eval:doc.enabled", - "fieldname": "section_break_3", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:!doc.__islocal", - "fieldname": "allow_google_access", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow GCalendar Access", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_4", - "fieldtype": "Section Break", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "refresh_token", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Refresh Token", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "authorization_code", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Authorization Code", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_6", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "session_token", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Session Token", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "state", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "State", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_9", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "gcalendar_id", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Google Calendar ID", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "next_sync_token", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Next Sync Token", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-10-04 13:32:27.673907", - "modified_by": "Administrator", - "module": "Integrations", - "name": "GCalendar Account", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 1, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "All", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/frappe/integrations/doctype/gcalendar_account/gcalendar_account.py b/frappe/integrations/doctype/gcalendar_account/gcalendar_account.py deleted file mode 100644 index bc2647dd49..0000000000 --- a/frappe/integrations/doctype/gcalendar_account/gcalendar_account.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, DOKOS and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document - -class GCalendarAccount(Document): - def validate(self): - if self.enabled == 1: - self.create_google_connector() - - def create_google_connector(self): - connector_name = 'Calendar Connector-' + self.name - if frappe.db.exists('Data Migration Connector', connector_name): - calendar_connector = frappe.get_doc('Data Migration Connector', connector_name) - calendar_connector.connector_type = 'Custom' - calendar_connector.python_module = 'frappe.data_migration.doctype.data_migration_connector.connectors.calendar_connector' - calendar_connector.username = self.name - calendar_connector.save() - return - - frappe.get_doc({ - 'doctype': 'Data Migration Connector', - 'connector_type': 'Custom', - 'connector_name': connector_name, - 'python_module': 'frappe.data_migration.doctype.data_migration_connector.connectors.calendar_connector', - 'username': self.name - }).insert() diff --git a/frappe/integrations/doctype/gcalendar_account/test_gcalendar_account.js b/frappe/integrations/doctype/gcalendar_account/test_gcalendar_account.js deleted file mode 100644 index 580b240c49..0000000000 --- a/frappe/integrations/doctype/gcalendar_account/test_gcalendar_account.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: GCalendar Account", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new GCalendar Account - () => frappe.tests.make('GCalendar Account', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/frappe/integrations/doctype/gcalendar_account/test_gcalendar_account.py b/frappe/integrations/doctype/gcalendar_account/test_gcalendar_account.py deleted file mode 100644 index 6ed4a95b12..0000000000 --- a/frappe/integrations/doctype/gcalendar_account/test_gcalendar_account.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, DOKOS and Contributors -# See license.txt -from __future__ import unicode_literals - -import frappe -import unittest - -class TestGCalendarAccount(unittest.TestCase): - def test_create_connector(self): - users = frappe.get_all("User") - doc = frappe.new_doc("GCalendar Account") - doc.enabled = 1 - doc.user = users[0].name - doc.calendar_name = "Frappe Test" - doc.save() - self.assertTrue(frappe.db.exists('GCalendar Account', users[0].name)) - - connector_name = 'Calendar Connector-' + users[0].name - self.assertTrue(frappe.db.exists('Data Migration Connector', connector_name)) diff --git a/frappe/integrations/doctype/gcalendar_settings/__init__.py b/frappe/integrations/doctype/gcalendar_settings/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/integrations/doctype/gcalendar_settings/gcalendar_settings.js b/frappe/integrations/doctype/gcalendar_settings/gcalendar_settings.js deleted file mode 100644 index 3be5603b9b..0000000000 --- a/frappe/integrations/doctype/gcalendar_settings/gcalendar_settings.js +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) 2017, DOKOS and contributors -// For license information, please see license.txt - -frappe.ui.form.on('GCalendar Settings', { - - -}); diff --git a/frappe/integrations/doctype/gcalendar_settings/gcalendar_settings.json b/frappe/integrations/doctype/gcalendar_settings/gcalendar_settings.json deleted file mode 100644 index 05111ec791..0000000000 --- a/frappe/integrations/doctype/gcalendar_settings/gcalendar_settings.json +++ /dev/null @@ -1,373 +0,0 @@ -{ - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-12-19 11:36:29.778694", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "System", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "enable", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Enable", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.enable", - "fieldname": "google_credentials", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Google API Credentials", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "client_id", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Client ID", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "client_secret", - "fieldtype": "Password", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Client Secret", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_7", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "refresh_token", - "fieldtype": "Password", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Refresh Token", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "authorization_code", - "fieldtype": "Password", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Authorization Code", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_10", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "session_token", - "fieldtype": "Password", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Session Token", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "state", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "state", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 1, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2018-02-16 11:21:11.643750", - "modified_by": "Administrator", - "module": "Integrations", - "name": "GCalendar Settings", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 1, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} diff --git a/frappe/integrations/doctype/gcalendar_settings/gcalendar_settings.py b/frappe/integrations/doctype/gcalendar_settings/gcalendar_settings.py deleted file mode 100644 index 0eff2b0f98..0000000000 --- a/frappe/integrations/doctype/gcalendar_settings/gcalendar_settings.py +++ /dev/null @@ -1,121 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, DOKOS and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document -from frappe import _ -from frappe.utils import get_request_site_address -import requests -import time -from frappe.utils.background_jobs import get_jobs - -if frappe.conf.developer_mode: - import os - os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' - -SCOPES = 'https://www.googleapis.com/auth/calendar' -AUTHORIZATION_BASE_URL = "https://accounts.google.com/o/oauth2/v2/auth" - -class GCalendarSettings(Document): - def sync(self): - """Create and execute Data Migration Run for GCalendar Sync plan""" - frappe.has_permission('GCalendar Settings', throw=True) - - - accounts = frappe.get_all("GCalendar Account", filters={'enabled': 1}) - - queued_jobs = get_jobs(site=frappe.local.site, key='job_name')[frappe.local.site] - for account in accounts: - job_name = 'google_calendar_sync|{0}'.format(account.name) - if job_name not in queued_jobs: - frappe.enqueue('frappe.integrations.doctype.gcalendar_settings.gcalendar_settings.run_sync', queue='long', timeout=1500, job_name=job_name, account=account) - time.sleep(5) - - def get_access_token(self): - if not self.refresh_token: - raise frappe.ValidationError(_("GCalendar is not configured.")) - data = { - 'client_id': self.client_id, - 'client_secret': self.get_password(fieldname='client_secret',raise_exception=False), - 'refresh_token': self.get_password(fieldname='refresh_token',raise_exception=False), - 'grant_type': "refresh_token", - 'scope': SCOPES - } - try: - r = requests.post('https://www.googleapis.com/oauth2/v4/token', data=data).json() - except requests.exceptions.HTTPError: - frappe.throw(_("Something went wrong during the token generation. Please request again an authorization code.")) - return r.get('access_token') - -@frappe.whitelist() -def sync(): - try: - gcalendar_settings = frappe.get_doc('GCalendar Settings') - if gcalendar_settings.enable == 1: - gcalendar_settings.sync() - except Exception: - frappe.log_error(frappe.get_traceback()) - -def run_sync(account): - exists = frappe.db.exists('Data Migration Run', dict(status=('in', ['Fail', 'Error']))) - if exists: - failed_run = frappe.get_doc("Data Migration Run", dict(status=('in', ['Fail', 'Error']))) - failed_run.delete() - - started = frappe.db.exists('Data Migration Run', dict(status=('in', ['Started']))) - if started: - return - - try: - doc = frappe.get_doc({ - 'doctype': 'Data Migration Run', - 'data_migration_plan': 'GCalendar Sync', - 'data_migration_connector': 'Calendar Connector-' + account.name - }).insert() - try: - doc.run() - except Exception: - frappe.log_error(frappe.get_traceback()) - except Exception: - frappe.log_error(frappe.get_traceback()) - -@frappe.whitelist() -def google_callback(code=None, state=None, account=None): - redirect_uri = get_request_site_address(True) + "?cmd=frappe.integrations.doctype.gcalendar_settings.gcalendar_settings.google_callback" - if account is not None: - frappe.cache().hset("gcalendar_account","GCalendar Account", account) - doc = frappe.get_doc("GCalendar Settings") - if code is None: - return { - 'url': 'https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&response_type=code&prompt=consent&client_id={}&include_granted_scopes=true&scope={}&redirect_uri={}'.format(doc.client_id, SCOPES, redirect_uri) - } - else: - try: - account = frappe.get_doc("GCalendar Account", frappe.cache().hget("gcalendar_account", "GCalendar Account")) - data = {'code': code, - 'client_id': doc.client_id, - 'client_secret': doc.get_password(fieldname='client_secret',raise_exception=False), - 'redirect_uri': redirect_uri, - 'grant_type': 'authorization_code'} - r = requests.post('https://www.googleapis.com/oauth2/v4/token', data=data).json() - frappe.db.set_value("GCalendar Account", account.name, "authorization_code", code) - if 'access_token' in r: - frappe.db.set_value("GCalendar Account", account.name, "session_token", r['access_token']) - if 'refresh_token' in r: - frappe.db.set_value("GCalendar Account", account.name, "refresh_token", r['refresh_token']) - frappe.db.commit() - frappe.local.response["type"] = "redirect" - frappe.local.response["location"] = "/integrations/gcalendar-success.html" - return - except Exception as e: - frappe.throw(e.message) - -@frappe.whitelist() -def refresh_token(token): - if 'refresh_token' in token: - frappe.db.set_value("GCalendar Settings", None, "refresh_token", token['refresh_token']) - if 'access_token' in token: - frappe.db.set_value("GCalendar Settings", None, "session_token", token['access_token']) - frappe.db.commit() diff --git a/frappe/integrations/doctype/gcalendar_settings/test_gcalendar_settings.js b/frappe/integrations/doctype/gcalendar_settings/test_gcalendar_settings.js deleted file mode 100644 index 23bd41ff9b..0000000000 --- a/frappe/integrations/doctype/gcalendar_settings/test_gcalendar_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: GCalendar Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new GCalendar Settings - () => frappe.tests.make('GCalendar Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/frappe/integrations/doctype/gcalendar_settings/test_gcalendar_settings.py b/frappe/integrations/doctype/gcalendar_settings/test_gcalendar_settings.py deleted file mode 100644 index cbc42a1c5f..0000000000 --- a/frappe/integrations/doctype/gcalendar_settings/test_gcalendar_settings.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, DOKOS and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -class TestGCalendarSettings(unittest.TestCase): - pass diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index ee8f64a03a..5f02f02eea 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -292,12 +292,12 @@ def google_calendar_insert_events(doc, method=None): try: pass - # google_calendar.events().insert(calendarId=account.google_calendar_id, body=event).execute() + google_calendar.events().insert(calendarId=account.google_calendar_id, body=event).execute() # frappe.db.set_value("Event", doc.name, "google_calendar_event", 1, update_modified=False) # frappe.db.set_value("Event", doc.name, "google_calendar_id", account.google_calendar_id, update_modified=False) # frappe.db.set_value("Event", doc.name, "google_calendar_event_id", event.get("id"), update_modified=False) except HttpError as e: - frappe.log_error(frappe.get_traceback(), _("Google Calendar - Could not insert event in Google Calendar.")) + frappe.log_error(e, _("Google Calendar - Could not insert event in Google Calendar.")) def google_calendar_update_events(doc, method=None): # Update Events with Google Calendar @@ -325,7 +325,7 @@ def google_calendar_update_events(doc, method=None): try: google_calendar.events().update(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id, body=event).execute() - except Exception as e: + except HttpError as e: frappe.log_error(e, "Google Calendar - Could not update event in Google Calendar.") def google_calendar_delete_events(doc, method=None): @@ -395,7 +395,7 @@ def google_calendar_to_repeat_on(start, end, recurrence=None): # Only Set starts_on for the event to repeat monthly start_date = parse_recurrence(int(repeat_day_week_number), repeat_day_name) repeat_on["starts_on"] = start_date - repeat_on["ends_on"] = add_to_date(minutes=5) + repeat_on["ends_on"] = add_to_date(start_date, minutes=5) repeat_on["repeat_till"] = None return repeat_on From 36f0bdce254d9e880bbbbba4bfc8801c0a6accb5 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Mon, 29 Jul 2019 10:31:02 +0530 Subject: [PATCH 104/203] Update rename_events_repeat_on.py --- frappe/patches/v12_0/rename_events_repeat_on.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/patches/v12_0/rename_events_repeat_on.py b/frappe/patches/v12_0/rename_events_repeat_on.py index b314ae663e..b0dface644 100644 --- a/frappe/patches/v12_0/rename_events_repeat_on.py +++ b/frappe/patches/v12_0/rename_events_repeat_on.py @@ -15,4 +15,4 @@ def execute(): for weekly_event in weekly_events: # Set WeekDay based on the starts_on so that event can repeat Weekly - frappe.db.sql("""UPDATE `tabEvent` SET `tabEvent`.{0}=1 WHERE `tabEvent`.name='{1}'""".format(weekdays[get_datetime(weekly_event.starts_on).weekday()], weekly_event.name)) \ No newline at end of file + frappe.db.set_value('Event', weekly_event.name, weekdays[get_datetime(weekly_event.starts_on).weekday()], 1, update_modified=False) From da6bcc0c22e3e8c43ce553f14ecbfb59a71c2360 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 29 Jul 2019 14:19:33 +0530 Subject: [PATCH 105/203] fix: not able to sace chat room --- frappe/chat/doctype/chat_room/chat_room.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/chat/doctype/chat_room/chat_room.py b/frappe/chat/doctype/chat_room/chat_room.py index b5873fa2d0..44a6ce0f0b 100644 --- a/frappe/chat/doctype/chat_room/chat_room.py +++ b/frappe/chat/doctype/chat_room/chat_room.py @@ -72,8 +72,9 @@ class ChatRoom(Document): def on_update(self): if not self.is_new(): before = self.get_doc_before_save() - after = self + if not before: return + after = self diff = dictify(get_diff(before, after)) if diff: update = { } From 5215ab09d84f6c65950681e8dd8a7e0e0cb8779b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 29 Jul 2019 14:58:54 +0530 Subject: [PATCH 106/203] fix: uploading files for web forms --- frappe/public/js/frappe/form/controls/attach.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/attach.js b/frappe/public/js/frappe/form/controls/attach.js index 00e9af4323..ef9aa1e05c 100644 --- a/frappe/public/js/frappe/form/controls/attach.js +++ b/frappe/public/js/frappe/form/controls/attach.js @@ -37,6 +37,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({ this.dataurl = null; this.fileobj = null; this.set_input(null); + this.parse_validate_and_set_in_model(null); this.refresh(); } }, @@ -92,7 +93,6 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({ this.frm.attachments.update_attachment(attachment); this.frm.doc.docstatus == 1 ? this.frm.save('Update') : this.frm.save(); } - this.value = attachment.file_url; - this.refresh(); + this.set_value(attachment.file_url); }, }); From 188ea01c556d880d1e8d3b1fb4e2bd4d7d4ec49c Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 29 Jul 2019 15:15:22 +0530 Subject: [PATCH 107/203] fix: translations for fields in webform list (#8026) * refactor: translated table fields * Update web_form_list.js * chore: added Sr to translated items --- frappe/public/js/frappe/web_form/web_form_list.js | 11 ++++------- frappe/website/doctype/web_form/web_form.py | 2 ++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/frappe/public/js/frappe/web_form/web_form_list.js b/frappe/public/js/frappe/web_form/web_form_list.js index c92b62f013..7b14844c6f 100644 --- a/frappe/public/js/frappe/web_form/web_form_list.js +++ b/frappe/public/js/frappe/web_form/web_form_list.js @@ -151,12 +151,9 @@ export default class WebFormList { th.appendChild(checkbox); row.appendChild(th); - add_heading(row, "Sr."); + add_heading(row, __("Sr")); this.columns.forEach(col => { - let th = document.createElement("th"); - let text = document.createTextNode(__(col.label)); - th.appendChild(text); - row.appendChild(th); + add_heading(row, __(col.label)); }); function add_heading(row, label) { @@ -301,7 +298,7 @@ frappe.ui.WebFormListRow = class WebFormListRow { let cell = this.row.insertCell(); let formatter = frappe.form.get_formatter(field.fieldtype); cell.innerHTML = this.doc[field.fieldname] && - formatter(this.doc[field.fieldname], field, {only_value: 1}, this.doc) || ""; + __(formatter(this.doc[field.fieldname], field, {only_value: 1}, this.doc)) || ""; }); this.row.onclick = () => this.events.onEdit(); @@ -316,4 +313,4 @@ frappe.ui.WebFormListRow = class WebFormListRow { is_selected() { return this.checkbox.checked; } -}; \ No newline at end of file +}; diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index 0ba80a4591..94159e13a4 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -177,6 +177,8 @@ def get_context(context): def load_translations(self, context): translated_messages = frappe.translate.get_dict('doctype', self.doc_type) + # Sr is not added by default, had to be added manually + translated_messages['Sr'] = _('Sr') context.translated_messages = frappe.as_json(translated_messages) def load_document(self, context): From 5fec5d7eea0b686a57da08ee3d47a11ef4f8803a Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 29 Jul 2019 15:42:51 +0530 Subject: [PATCH 108/203] fix: Check private file permissions for all docs A file may be attached to multiple documents. It's permission is decided based on the attached document's permissions. So, the permission should be checked for each document and should be allowed if atleast one document is accessible. --- frappe/core/doctype/file/file.py | 2 +- frappe/utils/response.py | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 8fac317100..3c3543e1dd 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -569,7 +569,7 @@ class File(NestedSet): if has_permission(self, 'read'): return True - raise frappe.PermissionError + return False def get_extension(self): '''returns split filename and extension''' diff --git a/frappe/utils/response.py b/frappe/utils/response.py index 7228e028ae..78cb3132d5 100644 --- a/frappe/utils/response.py +++ b/frappe/utils/response.py @@ -162,11 +162,19 @@ def download_backup(path): def download_private_file(path): """Checks permissions and sends back private file""" - try: - _file = frappe.get_doc("File", {"file_url": path}) - _file.is_downloadable() - except frappe.PermissionError: + files = frappe.db.get_all('File', {'file_url': path}) + can_access = False + # this file might be attached to multiple documents + # if the file is accessible from any one of those documents + # then it should be downloadable + for f in files: + _file = frappe.get_doc("File", f) + can_access = _file.is_downloadable() + if can_access: + break + + if not can_access: raise Forbidden(_("You don't have permission to access this file")) return send_private_file(path.split("/private", 1)[1]) From 6a8e82be4db81df7f725a791f1f94741c0c1074d Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 29 Jul 2019 16:45:22 +0530 Subject: [PATCH 109/203] feat: Google Calendar compatible with new events --- .../customize_form/test_customize_form.py | 2 +- frappe/hooks.py | 1 - .../google_calendar/google_calendar.py | 109 ++++++++++-------- 3 files changed, 64 insertions(+), 48 deletions(-) diff --git a/frappe/custom/doctype/customize_form/test_customize_form.py b/frappe/custom/doctype/customize_form/test_customize_form.py index 4547e81e4e..9fb5da2a6d 100644 --- a/frappe/custom/doctype/customize_form/test_customize_form.py +++ b/frappe/custom/doctype/customize_form/test_customize_form.py @@ -46,7 +46,7 @@ class TestCustomizeForm(unittest.TestCase): d = self.get_customize_form("Event") self.assertEquals(d.doc_type, "Event") - self.assertEquals(len(d.get("fields")), 29) + self.assertEquals(len(d.get("fields")), 33) d = self.get_customize_form("Event") self.assertEquals(d.doc_type, "Event") diff --git a/frappe/hooks.py b/frappe/hooks.py index f021c0815e..5e2f81ce0d 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -157,7 +157,6 @@ scheduler_events = { "frappe.oauth.delete_oauth2_data", "frappe.integrations.doctype.razorpay_settings.razorpay_settings.capture_payment", "frappe.twofactor.delete_all_barcodes_for_users", - "frappe.integrations.doctype.gcalendar_settings.gcalendar_settings.sync", "frappe.website.doctype.web_page.web_page.check_publish_status", 'frappe.utils.global_search.sync_global_search' ], diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 5f02f02eea..ce88912b01 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -15,15 +15,15 @@ from frappe.utils import get_request_site_address from googleapiclient.errors import HttpError from frappe.utils import add_days, add_years, get_datetime, get_weekdays, now_datetime, add_to_date, get_time_zone from dateutil import parser -from datetime import timedelta +from datetime import datetime, timedelta SCOPES = "https://www.googleapis.com/auth/calendar/v3" google_calendar_frequencies = { - "RRULE:FREQ=DAILY": "Every Day", - "RRULE:FREQ=WEEKLY": "Every Week", - "RRULE:FREQ=MONTHLY": "Every Month", - "RRULE:FREQ=YEARLY": "Every Year" + "RRULE:FREQ=DAILY": "Daily", + "RRULE:FREQ=WEEKLY": "Weekly", + "RRULE:FREQ=MONTHLY": "Monthly", + "RRULE:FREQ=YEARLY": "Yearly" } google_calendar_days = { @@ -37,10 +37,10 @@ google_calendar_days = { } framework_frequencies = { - "Every Day": "RRULE:FREQ=DAILY;", - "Every Week": "RRULE:FREQ=WEEKLY;", - "Every Month": "RRULE:FREQ=MONTHLY;", - "Every Year": "RRULE:FREQ=YEARLY;" + "Daily": "RRULE:FREQ=DAILY;", + "Weekly": "RRULE:FREQ=WEEKLY;", + "Monthly": "RRULE:FREQ=MONTHLY;", + "Yearly": "RRULE:FREQ=YEARLY;" } framework_days = { @@ -208,7 +208,7 @@ def check_remote_calendar(account, google_calendar): time.sleep(5) _create_calendar(account) else: - frappe.log_error(frappe.get_traceback(), _("Google Calendar Synchronization Error.")) + frappe.log_error(frappe.get_traceback(), _("Google Calendar - Could not create Calendar for {0}.").format(account.name)) def google_calendar_get_events(g_calendar, method=None, page_length=10): # Get Events from Google Calendar @@ -248,22 +248,26 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): frappe.publish_realtime('import_google_calendar', dict(progress=idx+1, total=len(results)), user=frappe.session.user) # If Google Calendar Event if confirmed, then create an Event - if event.get("status") == "confirmed" and not frappe.db.exists("Event", {"google_calendar_id": account.google_calendar_id, "google_event_id": event.get("id")}): + if event.get("status") == "confirmed" and not frappe.db.exists("Event", {"google_calendar_id": account.google_calendar_id, "google_calendar_event_id": event.get("id")}): + try: + recurrence = event.get('recurrence')[0] + except IndexError: + recurrence = None + calendar_event = { "doctype": "Event", "subject": event.get("summary"), "description": event.get("description"), "google_calendar_id": account.google_calendar_id, - "google_event_id": event.get("id"), + "google_calendar_event_id": event.get("id"), } - calendar_event.update(google_calendar_to_repeat_on(recurrence=event.get('recurrence')[0], start=event.get('start'), end=event.get('end'))) - # frappe.get_doc(event).insert(ignore_permissions=True) + calendar_event.update(google_calendar_to_repeat_on(recurrence=recurrence, start=event.get('start'), end=event.get('end'))) + + frappe.get_doc(calendar_event).insert(ignore_permissions=True) # If any synced Google Calendar Event is cancelled, then close the Event if event.get("status") == "cancelled": - # Close the issue status once new PR is merged - # frappe.db.set_value("Event", {"google_calendar_id": account.google_calendar_id, "google_event_id": event.get("id")}, "status", "Closed") - pass + frappe.db.set_value("Event", {"google_calendar_id": account.google_calendar_id, "google_calendar_event_id": event.get("id")}, "status", "Closed") def google_calendar_insert_events(doc, method=None): """ @@ -291,31 +295,31 @@ def google_calendar_insert_events(doc, method=None): event.update({"recurrence": unparse_recurrence(doc)}) try: - pass google_calendar.events().insert(calendarId=account.google_calendar_id, body=event).execute() - # frappe.db.set_value("Event", doc.name, "google_calendar_event", 1, update_modified=False) - # frappe.db.set_value("Event", doc.name, "google_calendar_id", account.google_calendar_id, update_modified=False) - # frappe.db.set_value("Event", doc.name, "google_calendar_event_id", event.get("id"), update_modified=False) + frappe.db.set_value("Event", doc.name, "google_calendar_event", 1, update_modified=False) + frappe.db.set_value("Event", doc.name, "google_calendar_id", account.google_calendar_id, update_modified=False) + frappe.db.set_value("Event", doc.name, "google_calendar_event_id", event.get("id"), update_modified=False) except HttpError as e: frappe.log_error(e, _("Google Calendar - Could not insert event in Google Calendar.")) def google_calendar_update_events(doc, method=None): # Update Events with Google Calendar - # Workaround to avoid triggering updation when Event is being inserted since # creation and modified are same when inserting doc - print("doc.modified, doc.creation") - print(doc.modified, doc.creation) - if doc.modified == doc.creation: - return if not frappe.db.exists("Google Calendar", {"user": frappe.session.user}): return + if doc.modified == doc.creation: + return + google_calendar, account = get_credentials({"user": frappe.session.user}) + if not account.push_to_google_calendar: + return + event = google_calendar.events().get(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id).execute() - event["summary"] = doc.summary + event["summary"] = doc.subject event["description"] = doc.description event["recurrence"] = unparse_recurrence(doc) event.update(google_calendar_format_date(get_datetime(doc.starts_on), get_datetime(doc.ends_on))) @@ -366,19 +370,21 @@ def google_calendar_to_repeat_on(start, end, recurrence=None): # recurrence rule "RRULE:FREQ=WEEKLY;BYDAY=MO,TU,TH" if recurrence: - # google_calendar_frequency = RRULE:FREQ=WEEKLY, repeat_days = BYDAY=MO,TU,TH - google_calendar_frequency = get_indexed_value(recurrence, 0) - repeat_days = get_indexed_value(recurrence, 1) - + # google_calendar_frequency = RRULE:FREQ=WEEKLY, repeat_days = BYDAY=MO,TU,TH, until = 20191028 + google_calendar_frequency, until, byday = get_recurrence_parameter(recurrence) repeat_on["repeat_on"] = google_calendar_frequencies.get(google_calendar_frequency) - repeat_on["repeat_till"] = get_datetime(end.get("date")) if end.get("date") else parser.parse(end.get("dateTime")).utcnow() - if repeat_days and repeat_on["repeat_on"] == "Every Week": + if repeat_on["repeat_on"] == "Daily": + repeat_on["ends_on"] = None + repeat_on["repeat_till"] = datetime.strptime(until, '%Y%m%d') if until else None + + if repeat_days and repeat_on["repeat_on"] == "Weekly": + repeat_on["repeat_till"] = datetime.strptime(until, '%Y%m%d') if until else None repeat_days = repeat_days.split("=")[1].split(",") for repeat_day in repeat_days: repeat_on[google_calendar_days[repeat_day]] = 1 - if repeat_days and repeat_on["repeat_on"] == "Every Month": + if repeat_days and repeat_on["repeat_on"] == "Monthly": repeat_days = repeat_days.split("=")[1] repeat_day_week_number, repeat_day_name = None, None @@ -396,7 +402,11 @@ def google_calendar_to_repeat_on(start, end, recurrence=None): start_date = parse_recurrence(int(repeat_day_week_number), repeat_day_name) repeat_on["starts_on"] = start_date repeat_on["ends_on"] = add_to_date(start_date, minutes=5) - repeat_on["repeat_till"] = None + repeat_on["repeat_till"] = datetime.strptime(until, '%Y%m%d') if until else None + + if repeat_on["repeat_till"] == "Yearly": + repeat_on["ends_on"] = None + repeat_on["repeat_till"] = datetime.strptime(until, '%Y%m%d') if until else None return repeat_on @@ -436,7 +446,7 @@ def parse_recurrence(repeat_day_week_number, repeat_day_name): isset_day_name = True if weekdays[current_date.weekday()].lower() == repeat_day_name else False current_date = add_days(current_date, 1) if not isset_day_name else current_date - # One the day is set ir Thursday, now set the week number + # One the day is set to Thursday, now set the week number ie 4 while not isset_day_number: week_number = get_week_number(current_date) isset_day_number = True if week_number == repeat_day_week_number else False @@ -451,10 +461,10 @@ def unparse_recurrence(doc): recurrence = framework_frequencies.get(doc.repeat_on) weekdays = get_weekdays() - if doc.repeat_on == "Every Week": + if doc.repeat_on == "Weekly": repeat_days = [framework_days.get(day.lower()) for day in weekdays if doc.get(day.lower())] recurrence = recurrence + "BYDAY=" + ",".join(repeat_days) - elif doc.repeat_on == "Every Month": + elif doc.repeat_on == "Monthly": week_number = str(get_week_number(get_datetime(doc.starts_on))) week_day = weekdays[get_datetime(doc.starts_on).weekday()].lower() recurrence = recurrence + "BYDAY=" + week_number + framework_days.get(week_day) @@ -474,15 +484,21 @@ def get_week_number(dt): return int(ceil(adjusted_dom/7.0)) -def get_indexed_value(d, index): - # Returns value from string based on index - if not d: - return None +def get_recurrence_parameter(recurrence): + recurrence = recurrence.split(";") + frequency, until, byday = None, None, None - try: - return d.split(";")[index] - except IndexError: - return None + for r in recurrence: + if "RRULE:FREQ" in r: + frequency = r + elif "UNTIL" in r: + until = r + elif "BYDAY" in r: + byday = r + else: + pass + + return frequency, until, byday """ API Response @@ -535,4 +551,5 @@ def get_indexed_value(d, index): - Weekly Event: ['RRULE:FREQ=WEEKLY;BYDAY=MO,TU,TH'] - Monthly Event: ['RRULE:FREQ=MONTHLY;BYDAY=4TH'] - Yearly Event: ['RRULE:FREQ=YEARLY;'] + - Custom Event: ['RRULE:FREQ=WEEKLY;WKST=SU;UNTIL=20191028;BYDAY=MO,WE'] """ \ No newline at end of file From 9fe7f5664a96fcb8ee6167e9cf880f0227335342 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Mon, 29 Jul 2019 15:09:27 +0530 Subject: [PATCH 110/203] fix: add document meta fields in webhook selection --- .../integrations/doctype/webhook/webhook.js | 51 +++++++++++-------- .../integrations/doctype/webhook/webhook.py | 14 +++-- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/frappe/integrations/doctype/webhook/webhook.js b/frappe/integrations/doctype/webhook/webhook.js index 0bcb99e5cf..d9e074f199 100644 --- a/frappe/integrations/doctype/webhook/webhook.js +++ b/frappe/integrations/doctype/webhook/webhook.js @@ -2,23 +2,29 @@ // For license information, please see license.txt frappe.webhook = { - set_fieldname_select: function(frm) { - var doc = frm.doc; - if (doc.webhook_doctype) { - frappe.model.with_doctype(doc.webhook_doctype, function() { - var fields = $.map(frappe.get_doc("DocType", frm.doc.webhook_doctype).fields, function(d) { - if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 || - frappe.model.table_fields.includes(d.fieldtype)) { + set_fieldname_select: (frm) => { + if (frm.doc.webhook_doctype) { + frappe.model.with_doctype(frm.doc.webhook_doctype, () => { + // get doctype fields + let fields = $.map(frappe.get_doc("DocType", frm.doc.webhook_doctype).fields, (d) => { + if (frappe.model.no_value_type.includes(d.fieldtype) || frappe.model.table_fields.includes(d.fieldtype)) { return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname }; - } - else if (d.fieldtype === 'Currency' || d.fieldtype === 'Float') { + } else if (d.fieldtype === 'Currency' || d.fieldtype === 'Float') { return { label: d.label, value: d.fieldname }; - } - else { + } else { return null; } }); - fields.unshift({"label":"Name (Doc Name)","value":"name"}); + + // add meta fields + for (let field of frappe.model.std_fields) { + if (field.fieldname == "name") { + fields.unshift({ label: "Name (Doc Name)", value: "name" }); + } else { + fields.push({ label: field.label + ' (' + field.fieldtype + ')', value: field.fieldname }); + } + } + frappe.meta.get_docfield("Webhook Data", "fieldname", frm.doc.name).options = [""].concat(fields); }); } @@ -26,21 +32,26 @@ frappe.webhook = { }; frappe.ui.form.on('Webhook', { - refresh: function(frm) { + refresh: (frm) => { frappe.webhook.set_fieldname_select(frm); }, - webhook_doctype: function(frm) { + + webhook_doctype: (frm) => { frappe.webhook.set_fieldname_select(frm); } }); frappe.ui.form.on("Webhook Data", { - fieldname: function(frm, doctype, name) { - var doc = frappe.get_doc(doctype, name); - var df = $.map(frappe.get_doc("DocType", frm.doc.webhook_doctype).fields, function(d) { - return doc.fieldname == d.fieldname ? d : null; - })[0]; - doc.key = df != undefined ? df.fieldname : "name"; + fieldname: (frm, cdt, cdn) => { + let row = locals[cdt][cdn]; + let df = frappe.get_meta(frm.doc.webhook_doctype).fields.filter((field) => field.fieldname == row.fieldname); + + if (!df.length) { + // check if field is a meta field + df = frappe.model.std_fields.filter((field) => field.fieldname == row.fieldname); + } + + row.key = df.length ? df[0].fieldname : "name"; frm.refresh_field("webhook_data"); } }); diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index 6ded0d0ac8..cd86e32272 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -3,12 +3,18 @@ # For license information, please see license.txt from __future__ import unicode_literals + +import datetime +import json +from time import sleep + +import requests +from six.moves.urllib.parse import urlparse + import frappe -import json, requests from frappe import _ from frappe.model.document import Document -from six.moves.urllib.parse import urlparse -from time import sleep + class Webhook(Document): def autoname(self): @@ -57,6 +63,8 @@ def enqueue_webhook(doc, webhook): for w in webhook.webhook_data: for k, v in doc.as_dict().items(): if k == w.fieldname: + if isinstance(v, datetime.datetime): + v = frappe.utils.get_datetime_str(v) data[w.key] = v for i in range(3): try: From a5113319c2796e35cc8cf223e03232870e4289bf Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 29 Jul 2019 17:35:13 +0530 Subject: [PATCH 111/203] fix: Add "fix_public_private_files" patch entry - Add additional exception handling --- frappe/patches.txt | 1 + frappe/patches/v12_0/fix_public_private_files.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/patches.txt b/frappe/patches.txt index dc12374903..0d5ccfcb8e 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -248,3 +248,4 @@ frappe.patches.v12_0.move_form_attachments_to_attachments_folder frappe.patches.v12_0.move_timeline_links_to_dynamic_links frappe.patches.v12_0.delete_feedback_request_if_exists #1 frappe.patches.v12_0.rename_events_repeat_on +frappe.patches.v12_0.fix_public_private_files \ No newline at end of file diff --git a/frappe/patches/v12_0/fix_public_private_files.py b/frappe/patches/v12_0/fix_public_private_files.py index 3f8f30379e..b70b905eb7 100644 --- a/frappe/patches/v12_0/fix_public_private_files.py +++ b/frappe/patches/v12_0/fix_public_private_files.py @@ -30,4 +30,6 @@ def generate_file(file_name): file_doc.file_url = new_doc.file_url file_doc.save() except IOError: - pass \ No newline at end of file + pass + except Exception as e: + print(e) \ No newline at end of file From 626f27206387aa119fce228e1ef066c09c801622 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 29 Jul 2019 18:59:51 +0530 Subject: [PATCH 112/203] fix: New File from awesomebar --- frappe/public/js/frappe/model/create_new.js | 6 ++++++ frappe/public/js/frappe/views/file/file_view.js | 7 ------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/frappe/public/js/frappe/model/create_new.js b/frappe/public/js/frappe/model/create_new.js index 7f409a4b0f..f577796d21 100644 --- a/frappe/public/js/frappe/model/create_new.js +++ b/frappe/public/js/frappe/model/create_new.js @@ -323,6 +323,12 @@ $.extend(frappe.model, { frappe.create_routes = {}; frappe.new_doc = function (doctype, opts, init_callback) { + if (doctype === 'File') { + new frappe.ui.FileUploader({ + folder: opts.folder || 'Home' + }); + return; + } return new Promise(resolve => { if(opts && $.isPlainObject(opts)) { frappe.route_options = opts; diff --git a/frappe/public/js/frappe/views/file/file_view.js b/frappe/public/js/frappe/views/file/file_view.js index 8d4e47a65c..fb01b87a61 100644 --- a/frappe/public/js/frappe/views/file/file_view.js +++ b/frappe/public/js/frappe/views/file/file_view.js @@ -296,13 +296,6 @@ frappe.views.FileView = class FileView extends frappe.views.ListView { `; } - make_new_doc() { - new frappe.ui.FileUploader({ - folder: this.current_folder, - on_success: () => this.refresh() - }); - } - setup_events() { super.setup_events(); this.setup_drag_drop(); From 83b266c36cfe44c2ca969292614a7f3e40b78f9f Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 29 Jul 2019 19:31:07 +0530 Subject: [PATCH 113/203] fix: rename repeat_days -> bydays --- .../google_calendar/google_calendar.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index ce88912b01..795efb8cb4 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -370,7 +370,7 @@ def google_calendar_to_repeat_on(start, end, recurrence=None): # recurrence rule "RRULE:FREQ=WEEKLY;BYDAY=MO,TU,TH" if recurrence: - # google_calendar_frequency = RRULE:FREQ=WEEKLY, repeat_days = BYDAY=MO,TU,TH, until = 20191028 + # google_calendar_frequency = RRULE:FREQ=WEEKLY, byday = BYDAY=MO,TU,TH, until = 20191028 google_calendar_frequency, until, byday = get_recurrence_parameter(recurrence) repeat_on["repeat_on"] = google_calendar_frequencies.get(google_calendar_frequency) @@ -378,23 +378,23 @@ def google_calendar_to_repeat_on(start, end, recurrence=None): repeat_on["ends_on"] = None repeat_on["repeat_till"] = datetime.strptime(until, '%Y%m%d') if until else None - if repeat_days and repeat_on["repeat_on"] == "Weekly": + if byday and repeat_on["repeat_on"] == "Weekly": repeat_on["repeat_till"] = datetime.strptime(until, '%Y%m%d') if until else None - repeat_days = repeat_days.split("=")[1].split(",") - for repeat_day in repeat_days: + byday = byday.split("=")[1].split(",") + for repeat_day in byday: repeat_on[google_calendar_days[repeat_day]] = 1 - if repeat_days and repeat_on["repeat_on"] == "Monthly": - repeat_days = repeat_days.split("=")[1] + if byday and repeat_on["repeat_on"] == "Monthly": + byday = byday.split("=")[1] repeat_day_week_number, repeat_day_name = None, None for num in ["1", "2", "3", "4", "5"]: - if num in repeat_days: + if num in byday: repeat_day_week_number = num break for day in ["MO","TU","WE","TH","FR","SA","SU"]: - if day in repeat_days: + if day in byday: repeat_day_name = google_calendar_days.get(day) break @@ -462,8 +462,8 @@ def unparse_recurrence(doc): weekdays = get_weekdays() if doc.repeat_on == "Weekly": - repeat_days = [framework_days.get(day.lower()) for day in weekdays if doc.get(day.lower())] - recurrence = recurrence + "BYDAY=" + ",".join(repeat_days) + byday = [framework_days.get(day.lower()) for day in weekdays if doc.get(day.lower())] + recurrence = recurrence + "BYDAY=" + ",".join(byday) elif doc.repeat_on == "Monthly": week_number = str(get_week_number(get_datetime(doc.starts_on))) week_day = weekdays[get_datetime(doc.starts_on).weekday()].lower() From 88e85fc9fde6c993f20541aece35736cf181f751 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 29 Jul 2019 20:12:49 +0530 Subject: [PATCH 114/203] fix: move function outside class --- .../google_calendar/google_calendar.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 795efb8cb4..7bcdad6c6e 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -55,25 +55,15 @@ framework_days = { class GoogleCalendar(Document): - def validate_google_settings(self): - google_settings = frappe.get_single("Google Settings") - if not google_settings.enable: - frappe.throw(_("Enable Google API in Google Settings.")) - - if not google_settings.client_id or not google_settings.client_secret: - frappe.throw(_("Enter Client Id and Client Secret in Google Settings.")) - - return google_settings - def validate(self): - self.validate_google_settings() + validate_google_settings() if frappe.db.exists("Google Calendar", {"user": self.user, "calendar_name": self.calendar_name}) and \ not frappe.db.get_value("Google Calendar", {"user": self.user, "calendar_name": self.calendar_name}, "name") == self.name: frappe.throw(_("Google Calendar already exists for user {0} and name {1}").format(self.user, self.calendar_name)) def get_access_token(self): - google_settings = self.validate_google_settings() + google_settings = validate_google_settings() if not self.refresh_token: button_label = frappe.bold(_("Allow Google Calendar Access")) @@ -95,6 +85,16 @@ class GoogleCalendar(Document): return r.get("access_token") +def validate_google_settings(): + google_settings = frappe.get_single("Google Settings") + if not google_settings.enable: + frappe.throw(_("Enable Google API in Google Settings.")) + + if not google_settings.client_id or not google_settings.client_secret: + frappe.throw(_("Enter Client Id and Client Secret in Google Settings.")) + + return google_settings + @frappe.whitelist() def authorize_access(g_calendar, reauthorize=None): """ @@ -304,12 +304,12 @@ def google_calendar_insert_events(doc, method=None): def google_calendar_update_events(doc, method=None): # Update Events with Google Calendar - # Workaround to avoid triggering updation when Event is being inserted since - # creation and modified are same when inserting doc if not frappe.db.exists("Google Calendar", {"user": frappe.session.user}): return + # Workaround to avoid triggering updation when Event is being inserted since + # creation and modified are same when inserting doc if doc.modified == doc.creation: return From c006312d9cf4bf88ddd35942904afdbdbb7d22f4 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 29 Jul 2019 21:09:21 +0530 Subject: [PATCH 115/203] fix: change fieldtypes to password --- .../google_calendar/google_calendar.json | 17 +++++++---------- .../doctype/google_calendar/google_calendar.py | 17 +++++++++-------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.json b/frappe/integrations/doctype/google_calendar/google_calendar.json index cecf8138cb..d8fdd425ce 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.json +++ b/frappe/integrations/doctype/google_calendar/google_calendar.json @@ -16,10 +16,9 @@ "cb_01", "push_to_google_calendar", "section_break_3", + "google_calendar_id", "refresh_token", "authorization_code", - "column_break_6", - "google_calendar_id", "next_sync_token" ], "fields": [ @@ -57,21 +56,19 @@ }, { "fieldname": "refresh_token", - "fieldtype": "Data", + "fieldtype": "Password", + "hidden": 1, "label": "Refresh Token" }, { "fieldname": "authorization_code", - "fieldtype": "Data", + "fieldtype": "Password", + "hidden": 1, "label": "Authorization Code" }, - { - "fieldname": "column_break_6", - "fieldtype": "Column Break" - }, { "fieldname": "next_sync_token", - "fieldtype": "Data", + "fieldtype": "Password", "hidden": 1, "label": "Next Sync Token" }, @@ -113,7 +110,7 @@ "label": "Push to Google Calendar" } ], - "modified": "2019-07-25 11:21:58.509798", + "modified": "2019-07-29 21:06:29.674579", "modified_by": "Administrator", "module": "Integrations", "name": "Google Calendar", diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 7bcdad6c6e..21db09f6cd 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -113,7 +113,7 @@ def authorize_access(g_calendar, reauthorize=None): else: try: data = { - "code": google_calendar.authorization_code, + "code": google_calendar.get_password(fieldname="authorization_code", raise_exception=False), "client_id": google_settings.client_id, "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), "redirect_uri": redirect_uri, @@ -166,7 +166,7 @@ def get_credentials(g_calendar): credentials_dict = { "token": account.get_access_token(), - "refresh_token": account.refresh_token, + "refresh_token": account.get_password(fieldname="refresh_token", raise_exception=False), "token_uri": "https://www.googleapis.com/oauth2/v4/token", "client_id": google_settings.client_id, "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), @@ -222,18 +222,15 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): while True: try: # API Response listed at EOF + sync_token = account.get_password(fieldname="next_sync_token", raise_exception=False) or None events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, - singleEvents=False, showDeleted=True, syncToken=account.next_sync_token or None).execute() - print("try") - print(events) + singleEvents=False, showDeleted=True, syncToken=sync_token).execute() except HttpError as err: if err.resp.status in [404, 410]: events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, singleEvents=False, showDeleted=True, timeMin=add_years(None, -1).strftime("%Y-%m-%dT%H:%M:%SZ")).execute() - print("except") - print(events) else: - frappe.log_error(err.resp, "Google Calendar Events Fetch Error.") + frappe.log_error(err, "Google Calendar - Could not fetch event from Google Calendar.") for event in events.get("items"): results.append(event) @@ -258,6 +255,7 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): "doctype": "Event", "subject": event.get("summary"), "description": event.get("description"), + "google_calendar_event": 1, "google_calendar_id": account.google_calendar_id, "google_calendar_event_id": event.get("id"), } @@ -340,6 +338,9 @@ def google_calendar_delete_events(doc, method=None): google_calendar, account = get_credentials({"user": frappe.session.user}) + if not account.push_to_google_calendar: + return + try: google_calendar.events().delete(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id).execute() except Exception as e: From 450009e9172ade2c938a9a38a9e6c6bae490a521 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 30 Jul 2019 12:13:45 +0530 Subject: [PATCH 116/203] fix: added null check for contact attributes --- frappe/email/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/email/__init__.py b/frappe/email/__init__.py index 494a6e2bb3..f99536f9a8 100644 --- a/frappe/email/__init__.py +++ b/frappe/email/__init__.py @@ -91,7 +91,7 @@ def get_cached_contacts(txt): if not txt: return contacts - match = [d for d in contacts if (d.value and (txt in d.value or txt in d.description))] + match = [d for d in contacts if (d.value and ((d.value and txt in d.value) or (d.description and txt in d.description)))] return match def update_contact_cache(contacts): From 472d33f3da5e9410d9fc4d2e50e154d0794a4885 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Mon, 29 Jul 2019 17:07:09 +0530 Subject: [PATCH 117/203] fix(security): Make Jinja Tighter --- frappe/utils/jinja.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py index 7a27fb3c3b..28e3b3d463 100644 --- a/frappe/utils/jinja.py +++ b/frappe/utils/jinja.py @@ -6,10 +6,11 @@ def get_jenv(): import frappe if not getattr(frappe.local, 'jenv', None): - from jinja2 import Environment, DebugUndefined + from jinja2 import DebugUndefined + from jinja2.sandbox import SandboxedEnvironment # frappe will be loaded last, so app templates will get precedence - jenv = Environment(loader = get_jloader(), + jenv = SandboxedEnvironment(loader = get_jloader(), undefined=DebugUndefined) set_filters(jenv) From 8ac155f7b6d180f8ba7da6d5678e51cb2316f9a7 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Mon, 29 Jul 2019 20:22:04 +0530 Subject: [PATCH 118/203] fix(security): Sanitize fields list, group_by and order_by clause to prevent SQLi --- frappe/model/db_query.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 53a1c6c13d..74bd245fb4 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -115,8 +115,12 @@ class DatabaseQuery(object): args.fields = 'distinct ' + args.fields args.order_by = '' # TODO: recheck for alternative - query = """select %(fields)s from %(tables)s %(conditions)s - %(group_by)s %(order_by)s %(limit)s""" % args + query = """select %(fields)s + from %(tables)s + %(conditions)s + %(group_by)s + %(order_by)s + %(limit)s""" % args if self.return_query: return query @@ -240,6 +244,11 @@ class DatabaseQuery(object): _is_query(field) + if re.compile(r".*/\*.*").match(field): + frappe.throw(_('Illegal SQL Query')) + + if re.compile(r".*\s(union).*\s").match(field.lower()): + frappe.throw(_('Illegal SQL Query')) def extract_tables(self): """extract tables from fields""" @@ -688,6 +697,8 @@ class DatabaseQuery(object): if 'select' in _lower and ' from ' in _lower: frappe.throw(_('Cannot use sub-query in order by')) + if re.compile(r".*[^a-z0-9-_ ,`'\"\.\(\)].*").match(_lower): + frappe.throw(_('Illegal SQL Query')) for field in parameters.split(","): if "." in field and field.strip().startswith("`tab"): From 9a6ec1ea5a2f5d9c54e0c2625d306db242703817 Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Tue, 30 Jul 2019 13:28:13 +0550 Subject: [PATCH 119/203] bumped to version 12.0.3 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index a761daa143..e888379ecd 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -23,7 +23,7 @@ if sys.version[0] == '2': reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '12.0.2' +__version__ = '12.0.3' __title__ = "Frappe Framework" local = Local() From 5d04fb4eb791cac5ae7a33f15577b2d42aa6ed2f Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 30 Jul 2019 14:09:00 +0530 Subject: [PATCH 120/203] fix(search): Reduce restrictions on field contents --- frappe/desk/reportview.py | 1 + frappe/desk/search.py | 3 ++- frappe/model/db_query.py | 13 ++++++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 9654e14687..189178d878 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -68,6 +68,7 @@ def get_form_params(): # queries must always be server side data.query = None + data.strict = None return data diff --git a/frappe/desk/search.py b/frappe/desk/search.py index 61b0cf2905..18278f7871 100644 --- a/frappe/desk/search.py +++ b/frappe/desk/search.py @@ -153,7 +153,8 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0, order_by=order_by, ignore_permissions=ignore_permissions, reference_doctype=reference_doctype, - as_list=not as_dict) + as_list=not as_dict, + strict=False) if doctype in UNTRANSLATED_DOCTYPES: values = tuple([v for v in list(values) if re.search(txt+".*", (_(v.name) if as_dict else _(v[0])), re.IGNORECASE)]) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 74bd245fb4..f864e4f356 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -36,7 +36,7 @@ class DatabaseQuery(object): ignore_permissions=False, user=None, with_comment_count=False, join='left join', distinct=False, start=None, page_length=None, limit=None, ignore_ifnull=False, save_user_settings=False, save_user_settings_fields=False, - update=None, add_total_row=None, user_settings=None, reference_doctype=None, return_query=False): + update=None, add_total_row=None, user_settings=None, reference_doctype=None, return_query=False, strict=True): if not ignore_permissions and not frappe.has_permission(self.doctype, "read", user=user): frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(self.doctype)) raise frappe.PermissionError(self.doctype) @@ -80,6 +80,7 @@ class DatabaseQuery(object): self.update = update self.user_settings_fields = copy.deepcopy(self.fields) self.return_query = return_query + self.strict = strict # for contextual user permission check # to determine which user permission is applicable on link field of specific doctype @@ -244,11 +245,12 @@ class DatabaseQuery(object): _is_query(field) - if re.compile(r".*/\*.*").match(field): - frappe.throw(_('Illegal SQL Query')) + if self.strict: + if re.compile(r".*/\*.*").match(field): + frappe.throw(_('Illegal SQL Query')) - if re.compile(r".*\s(union).*\s").match(field.lower()): - frappe.throw(_('Illegal SQL Query')) + if re.compile(r".*\s(union).*\s").match(field.lower()): + frappe.throw(_('Illegal SQL Query')) def extract_tables(self): """extract tables from fields""" @@ -766,6 +768,7 @@ def get_list(doctype, *args, **kwargs): kwargs.pop('cmd', None) kwargs.pop('ignore_permissions', None) kwargs.pop('data', None) + kwargs.pop('strict', None) # If doctype is child table if frappe.is_table(doctype): From cddbb005e2a23e0d008d388c75b6d783b09290c8 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 30 Jul 2019 14:45:14 +0530 Subject: [PATCH 121/203] fix: code cleanup --- frappe/hooks.py | 1 + .../google_calendar/google_calendar.py | 96 +++++++++++-------- 2 files changed, 59 insertions(+), 38 deletions(-) diff --git a/frappe/hooks.py b/frappe/hooks.py index 5e2f81ce0d..d53704738d 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -169,6 +169,7 @@ scheduler_events = { "frappe.limits.update_site_usage", "frappe.deferred_insert.save_to_db", "frappe.desk.form.document_follow.send_hourly_updates", + "frappe.integrations.doctype.google_calendar.google_calendar.sync" ], "daily": [ "frappe.email.queue.clear_outbox", diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 21db09f6cd..24db136936 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -212,6 +212,7 @@ def check_remote_calendar(account, google_calendar): def google_calendar_get_events(g_calendar, method=None, page_length=10): # Get Events from Google Calendar + google_calendar, account = get_credentials({"name": g_calendar}) if not account.pull_from_google_calendar: @@ -226,7 +227,7 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, singleEvents=False, showDeleted=True, syncToken=sync_token).execute() except HttpError as err: - if err.resp.status in [404, 410]: + if err.resp.status in [400, 404, 410]: events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, singleEvents=False, showDeleted=True, timeMin=add_years(None, -1).strftime("%Y-%m-%dT%H:%M:%SZ")).execute() else: @@ -258,25 +259,29 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): "google_calendar_event": 1, "google_calendar_id": account.google_calendar_id, "google_calendar_event_id": event.get("id"), + "owner": event.get("creator").get("email") } calendar_event.update(google_calendar_to_repeat_on(recurrence=recurrence, start=event.get('start'), end=event.get('end'))) - frappe.get_doc(calendar_event).insert(ignore_permissions=True) - - # If any synced Google Calendar Event is cancelled, then close the Event - if event.get("status") == "cancelled": + elif event.get("status") == "cancelled": + # If any synced Google Calendar Event is cancelled, then close the Event frappe.db.set_value("Event", {"google_calendar_id": account.google_calendar_id, "google_calendar_event_id": event.get("id")}, "status", "Closed") + else: + pass def google_calendar_insert_events(doc, method=None): - """ - Insert Events to Google Calendar - UUID algorithm used minimize the risk of id collisions such as one described in RFC4122. - https://developers.google.com/calendar/v3/reference/events/insert - """ - if not frappe.db.exists("Google Calendar", {"user": frappe.session.user}): + # Insert Events to Google Calendar + + def _google_calendar_insert_events(google_calendar, account, event, doc): + event = google_calendar.events().insert(calendarId=account.google_calendar_id, body=event).execute() + frappe.db.set_value("Event", doc.name, "google_calendar_event", 1, update_modified=False) + frappe.db.set_value("Event", doc.name, "google_calendar_id", account.google_calendar_id, update_modified=False) + frappe.db.set_value("Event", doc.name, "google_calendar_event_id", event.get("id"), update_modified=False) + + if not frappe.db.exists("Google Calendar", {"user": doc.owner or frappe.session.user}): return - google_calendar, account = get_credentials({"user": frappe.session.user}) + google_calendar, account = get_credentials({"user": doc.owner or frappe.session.user}) if not account.push_to_google_calendar: return @@ -284,7 +289,6 @@ def google_calendar_insert_events(doc, method=None): event = { "summary": doc.subject, "description": doc.description, - "id": str(uuid.uuid5(uuid.NAMESPACE_DNS, doc.name)), "google_calendar_event": 1 } event.update(google_calendar_format_date(get_datetime(doc.starts_on), get_datetime(doc.ends_on))) @@ -293,17 +297,30 @@ def google_calendar_insert_events(doc, method=None): event.update({"recurrence": unparse_recurrence(doc)}) try: - google_calendar.events().insert(calendarId=account.google_calendar_id, body=event).execute() - frappe.db.set_value("Event", doc.name, "google_calendar_event", 1, update_modified=False) - frappe.db.set_value("Event", doc.name, "google_calendar_id", account.google_calendar_id, update_modified=False) - frappe.db.set_value("Event", doc.name, "google_calendar_event_id", event.get("id"), update_modified=False) - except HttpError as e: - frappe.log_error(e, _("Google Calendar - Could not insert event in Google Calendar.")) + _google_calendar_insert_events(google_calendar, account, event, doc) + except HttpError as err: + if err.resp.status in [400, 404, 410]: + _google_calendar_insert_events(google_calendar, account, event, doc) + else: + frappe.log_error(err, _("Google Calendar - Could not insert event in Google Calendar.")) + def google_calendar_update_events(doc, method=None): # Update Events with Google Calendar - if not frappe.db.exists("Google Calendar", {"user": frappe.session.user}): + def _google_calendar_update_events(google_calendar, account, doc): + event = google_calendar.events().get(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id).execute() + event["summary"] = doc.subject + event["description"] = doc.description + event["recurrence"] = unparse_recurrence(doc) + event.update(google_calendar_format_date(get_datetime(doc.starts_on), get_datetime(doc.ends_on))) + + if doc.event_type == "Cancelled" or doc.status == "Closed": + event["status"] = "cancelled" + + google_calendar.events().update(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id, body=event).execute() + + if not frappe.db.exists("Google Calendar", {"user": doc.owner or frappe.session.user}): return # Workaround to avoid triggering updation when Event is being inserted since @@ -311,40 +328,43 @@ def google_calendar_update_events(doc, method=None): if doc.modified == doc.creation: return - google_calendar, account = get_credentials({"user": frappe.session.user}) + google_calendar, account = get_credentials({"user": doc.owner or frappe.session.user}) if not account.push_to_google_calendar: return - event = google_calendar.events().get(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id).execute() - event["summary"] = doc.subject - event["description"] = doc.description - event["recurrence"] = unparse_recurrence(doc) - event.update(google_calendar_format_date(get_datetime(doc.starts_on), get_datetime(doc.ends_on))) - - if doc.event_type == "Cancelled" or doc.status == "Closed": - event["status"] = "cancelled" - try: - google_calendar.events().update(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id, body=event).execute() - except HttpError as e: - frappe.log_error(e, "Google Calendar - Could not update event in Google Calendar.") + _google_calendar_update_events(google_calendar, account, doc) + except HttpError as err: + if err.resp.status in [400, 404, 410]: + _google_calendar_update_events(google_calendar, account, doc) + else: + frappe.log_error(err, "Google Calendar - Could not update Event {0} in Google Calendar.".format(doc.name)) def google_calendar_delete_events(doc, method=None): # Delete Events from Google Calendar - if not frappe.db.exists("Google Calendar", {"user": frappe.session.user}): + def _google_calendar_delete_events(google_calendar, account, doc): + events = google_calendar.events().get(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id).execute() + events["recurrence"] = None + events["status"] = "cancelled" + google_calendar.events().update(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id, body=event).execute() + + if not frappe.db.exists("Google Calendar", {"user": doc.owner or frappe.session.user}): return - google_calendar, account = get_credentials({"user": frappe.session.user}) + google_calendar, account = get_credentials({"user": doc.owner or frappe.session.user}) if not account.push_to_google_calendar: return try: - google_calendar.events().delete(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id).execute() - except Exception as e: - frappe.log_error(e, "Google Calendar - Could not delete event from Google Calendar.") + _google_calendar_delete_events(google_calendar, account, doc) + except Exception as err: + if err.resp.status in [400, 404, 410]: + _google_calendar_delete_events(google_calendar, account, doc) + else: + frappe.log_error(err, "Google Calendar - Could not delete Event {0} from Google Calendar.".format(doc.name)) def google_calendar_to_repeat_on(start, end, recurrence=None): """ From dca19472aae2408f41dd00878906489a256fc499 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Tue, 30 Jul 2019 17:01:59 +0530 Subject: [PATCH 122/203] fix: Enqueue personal data file generation for faster response (#7977) --- .../personal_data_download_request.py | 4 +++- .../test_personal_data_download_request.py | 23 ++++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/frappe/website/doctype/personal_data_download_request/personal_data_download_request.py b/frappe/website/doctype/personal_data_download_request/personal_data_download_request.py index d9b2856854..747b7adbbe 100644 --- a/frappe/website/doctype/personal_data_download_request/personal_data_download_request.py +++ b/frappe/website/doctype/personal_data_download_request/personal_data_download_request.py @@ -12,7 +12,9 @@ from frappe.utils.verified_command import get_signed_params class PersonalDataDownloadRequest(Document): def after_insert(self): personal_data = get_user_data(self.user) - self.generate_file_and_send_mail(personal_data) + + frappe.enqueue_doc(self.doctype, self.name, 'generate_file_and_send_mail', + queue='short', personal_data=personal_data, now=frappe.flags.in_test) def generate_file_and_send_mail(self, personal_data): """generate the file link for download""" diff --git a/frappe/website/doctype/personal_data_download_request/test_personal_data_download_request.py b/frappe/website/doctype/personal_data_download_request/test_personal_data_download_request.py index 32b86bca0b..64d4e45660 100644 --- a/frappe/website/doctype/personal_data_download_request/test_personal_data_download_request.py +++ b/frappe/website/doctype/personal_data_download_request/test_personal_data_download_request.py @@ -21,18 +21,25 @@ class TestRequestPersonalData(unittest.TestCase): def test_file_and_email_creation(self): frappe.set_user('test_privacy@example.com') - download_request = frappe.get_doc({"doctype": 'Personal Data Download Request', 'user': 'test_privacy@example.com'}) + download_request = frappe.get_doc({ + "doctype": 'Personal Data Download Request', + 'user': 'test_privacy@example.com' + }) download_request.save(ignore_permissions=True) + frappe.set_user('Administrator') - f = frappe.get_all('File', - {'attached_to_doctype':'Personal Data Download Request', 'attached_to_name': download_request.name}, - ['*']) - self.assertEqual(len(f), 1) + file_count = frappe.db.count('File', { + 'attached_to_doctype':'Personal Data Download Request', + 'attached_to_name': download_request.name + }) - email_queue = frappe.db.sql("""SELECT * - FROM `tabEmail Queue` - ORDER BY `creation` DESC""", as_dict=True) + self.assertEqual(file_count, 1) + + email_queue = frappe.get_all('Email Queue', + fields=['message'], + order_by="creation DESC", + limit=1) self.assertTrue("Subject: Download Your Data" in email_queue[0].message) frappe.db.sql("delete from `tabEmail Queue`") From baa8f1be99ca0162c9267522024532885d49957d Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 30 Jul 2019 17:55:22 +0530 Subject: [PATCH 123/203] fix: Module check in get_desktop_settings (#8053) --- frappe/desk/moduleview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/moduleview.py b/frappe/desk/moduleview.py index ca3eef5c52..bdd1115c66 100644 --- a/frappe/desk/moduleview.py +++ b/frappe/desk/moduleview.py @@ -336,7 +336,7 @@ def get_desktop_settings(): if category in user_saved_modules_by_category: user_modules = user_saved_modules_by_category[category] user_modules_by_category[category] = [apply_user_saved_links(modules_by_name[m]) \ - for m in user_modules] + for m in user_modules if modules_by_name.get(m)] else: user_modules_by_category[category] = [apply_user_saved_links(m) \ for m in all_modules if m.get('category') == category] From f79a44bdd68c100ce99bd66002db33fe5464c058 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 30 Jul 2019 19:52:06 +0530 Subject: [PATCH 124/203] fix: delete recurrence event --- .../doctype/google_calendar/google_calendar.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 24db136936..8ac93f8c94 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -228,6 +228,7 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): singleEvents=False, showDeleted=True, syncToken=sync_token).execute() except HttpError as err: if err.resp.status in [400, 404, 410]: + time.sleep(5) events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, singleEvents=False, showDeleted=True, timeMin=add_years(None, -1).strftime("%Y-%m-%dT%H:%M:%SZ")).execute() else: @@ -246,7 +247,7 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): frappe.publish_realtime('import_google_calendar', dict(progress=idx+1, total=len(results)), user=frappe.session.user) # If Google Calendar Event if confirmed, then create an Event - if event.get("status") == "confirmed" and not frappe.db.exists("Event", {"google_calendar_id": account.google_calendar_id, "google_calendar_event_id": event.get("id")}): + if event.get("status") == "confirmed": try: recurrence = event.get('recurrence')[0] except IndexError: @@ -300,6 +301,7 @@ def google_calendar_insert_events(doc, method=None): _google_calendar_insert_events(google_calendar, account, event, doc) except HttpError as err: if err.resp.status in [400, 404, 410]: + time.sleep(2) _google_calendar_insert_events(google_calendar, account, event, doc) else: frappe.log_error(err, _("Google Calendar - Could not insert event in Google Calendar.")) @@ -337,6 +339,7 @@ def google_calendar_update_events(doc, method=None): _google_calendar_update_events(google_calendar, account, doc) except HttpError as err: if err.resp.status in [400, 404, 410]: + time.sleep(2) _google_calendar_update_events(google_calendar, account, doc) else: frappe.log_error(err, "Google Calendar - Could not update Event {0} in Google Calendar.".format(doc.name)) @@ -345,9 +348,9 @@ def google_calendar_delete_events(doc, method=None): # Delete Events from Google Calendar def _google_calendar_delete_events(google_calendar, account, doc): - events = google_calendar.events().get(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id).execute() - events["recurrence"] = None - events["status"] = "cancelled" + event = google_calendar.events().get(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id).execute() + event["recurrence"] = None + event["status"] = "cancelled" google_calendar.events().update(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id, body=event).execute() if not frappe.db.exists("Google Calendar", {"user": doc.owner or frappe.session.user}): @@ -360,8 +363,9 @@ def google_calendar_delete_events(doc, method=None): try: _google_calendar_delete_events(google_calendar, account, doc) - except Exception as err: + except HttpError as err: if err.resp.status in [400, 404, 410]: + time.sleep(2) _google_calendar_delete_events(google_calendar, account, doc) else: frappe.log_error(err, "Google Calendar - Could not delete Event {0} from Google Calendar.".format(doc.name)) From 15f77e492162578c86f78ddf16914535a6ce5895 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 30 Jul 2019 22:16:24 +0530 Subject: [PATCH 125/203] fix: do not set owner for event --- frappe/integrations/doctype/google_calendar/google_calendar.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 8ac93f8c94..54c705617d 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -259,8 +259,7 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): "description": event.get("description"), "google_calendar_event": 1, "google_calendar_id": account.google_calendar_id, - "google_calendar_event_id": event.get("id"), - "owner": event.get("creator").get("email") + "google_calendar_event_id": event.get("id") } calendar_event.update(google_calendar_to_repeat_on(recurrence=recurrence, start=event.get('start'), end=event.get('end'))) frappe.get_doc(calendar_event).insert(ignore_permissions=True) From 921788474ec3c9fbf63158a7171cbe2e2173c825 Mon Sep 17 00:00:00 2001 From: Ashish Shah Date: Wed, 31 Jul 2019 10:17:19 +0530 Subject: [PATCH 126/203] Update receive.py +existing code, works fine in python 2.7 +in python 3 it breaks +In python 3 there is change in chardet module and hence the error +convert-string-to-bytes-in-python-3 +this is also fixed in v11-hotfix branch --- frappe/email/receive.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/email/receive.py b/frappe/email/receive.py index dcd21d3c10..a95975c4b0 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -481,7 +481,10 @@ class Email: """Detect chartset.""" charset = part.get_content_charset() if not charset: - charset = chardet.detect(str(part))['encoding'] + if six.PY2: + charset = chardet.detect(str(part))['encoding'] + else: + charset = chardet.detect(part.encode())['encoding'] return charset From 03c10950388fd0909136e0e6d3ce00f61a591268 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 31 Jul 2019 10:32:40 +0530 Subject: [PATCH 127/203] test: test case fixes --- frappe/custom/doctype/customize_form/test_customize_form.py | 2 +- frappe/integrations/doctype/google_calendar/google_calendar.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/custom/doctype/customize_form/test_customize_form.py b/frappe/custom/doctype/customize_form/test_customize_form.py index 9fb5da2a6d..c72e167fab 100644 --- a/frappe/custom/doctype/customize_form/test_customize_form.py +++ b/frappe/custom/doctype/customize_form/test_customize_form.py @@ -46,7 +46,7 @@ class TestCustomizeForm(unittest.TestCase): d = self.get_customize_form("Event") self.assertEquals(d.doc_type, "Event") - self.assertEquals(len(d.get("fields")), 33) + self.assertEquals(len(d.get("fields")), 34) d = self.get_customize_form("Event") self.assertEquals(d.doc_type, "Event") diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 54c705617d..fd0b91500f 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -8,7 +8,7 @@ import requests import googleapiclient.discovery import google.oauth2.credentials import time -import uuid + from frappe import _ from frappe.model.document import Document from frappe.utils import get_request_site_address @@ -576,4 +576,3 @@ def get_recurrence_parameter(recurrence): - Monthly Event: ['RRULE:FREQ=MONTHLY;BYDAY=4TH'] - Yearly Event: ['RRULE:FREQ=YEARLY;'] - Custom Event: ['RRULE:FREQ=WEEKLY;WKST=SU;UNTIL=20191028;BYDAY=MO,WE'] -""" \ No newline at end of file From 53527e0483d5d36077e0329a2c71c974e9dc2e85 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 31 Jul 2019 11:46:48 +0530 Subject: [PATCH 128/203] fix: do not insert synced events --- .../customize_form/test_customize_form.py | 2 +- frappe/desk/doctype/event/event.json | 10 ++++++- .../google_calendar/google_calendar.js | 2 +- .../google_calendar/google_calendar.py | 29 +++++++++---------- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/frappe/custom/doctype/customize_form/test_customize_form.py b/frappe/custom/doctype/customize_form/test_customize_form.py index c72e167fab..1cd71ea05d 100644 --- a/frappe/custom/doctype/customize_form/test_customize_form.py +++ b/frappe/custom/doctype/customize_form/test_customize_form.py @@ -46,7 +46,7 @@ class TestCustomizeForm(unittest.TestCase): d = self.get_customize_form("Event") self.assertEquals(d.doc_type, "Event") - self.assertEquals(len(d.get("fields")), 34) + self.assertEquals(len(d.get("fields")), 35) d = self.get_customize_form("Event") self.assertEquals(d.doc_type, "Event") diff --git a/frappe/desk/doctype/event/event.json b/frappe/desk/doctype/event/event.json index e94dc38252..d725a107e3 100644 --- a/frappe/desk/doctype/event/event.json +++ b/frappe/desk/doctype/event/event.json @@ -37,6 +37,7 @@ "event_participants", "sb_00", "google_calendar_id", + "synced_from_google_calendar", "cb_00", "google_calendar_event_id" ], @@ -252,11 +253,18 @@ "fieldtype": "Check", "label": "Google Calendar Event", "read_only": 1 + }, + { + "default": "0", + "fieldname": "synced_from_google_calendar", + "fieldtype": "Check", + "label": "Synced from Google Calendar", + "read_only": 1 } ], "icon": "fa fa-calendar", "idx": 1, - "modified": "2019-07-28 14:22:27.221143", + "modified": "2019-07-31 11:01:08.920725", "modified_by": "Administrator", "module": "Desk", "name": "Event", diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.js b/frappe/integrations/doctype/google_calendar/google_calendar.js index 8a903888db..e0dce4d4ba 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.js +++ b/frappe/integrations/doctype/google_calendar/google_calendar.js @@ -28,7 +28,7 @@ frappe.ui.form.on('Google Calendar', { }, }).then((r) => { frappe.hide_progress(); - frappe.msgprint(r.message); + frappe.msgprint("Google Calendar Events Synced"); }); }); } diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index fd0b91500f..6071666d32 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -204,7 +204,7 @@ def check_remote_calendar(account, google_calendar): else: _create_calendar(account) except HttpError as err: - if err.resp.status in [403, 404, 500, 503]: + if err.resp.status in [403, 404, 410, 500, 503]: time.sleep(5) _create_calendar(account) else: @@ -227,12 +227,12 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, singleEvents=False, showDeleted=True, syncToken=sync_token).execute() except HttpError as err: - if err.resp.status in [400, 404, 410]: + if err.resp.status in [400, 404, 410, 500, 503]: time.sleep(5) events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, singleEvents=False, showDeleted=True, timeMin=add_years(None, -1).strftime("%Y-%m-%dT%H:%M:%SZ")).execute() else: - frappe.log_error(err, "Google Calendar - Could not fetch event from Google Calendar.") + frappe.throw(_("Google Calendar - Could not fetch event from Google Calendar.")) for event in events.get("items"): results.append(event) @@ -259,7 +259,8 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): "description": event.get("description"), "google_calendar_event": 1, "google_calendar_id": account.google_calendar_id, - "google_calendar_event_id": event.get("id") + "google_calendar_event_id": event.get("id"), + "synced_from_google_calendar": 1 } calendar_event.update(google_calendar_to_repeat_on(recurrence=recurrence, start=event.get('start'), end=event.get('end'))) frappe.get_doc(calendar_event).insert(ignore_permissions=True) @@ -278,7 +279,7 @@ def google_calendar_insert_events(doc, method=None): frappe.db.set_value("Event", doc.name, "google_calendar_id", account.google_calendar_id, update_modified=False) frappe.db.set_value("Event", doc.name, "google_calendar_event_id", event.get("id"), update_modified=False) - if not frappe.db.exists("Google Calendar", {"user": doc.owner or frappe.session.user}): + if not frappe.db.exists("Google Calendar", {"user": doc.owner or frappe.session.user}) or doc.synced_from_google_calendar: return google_calendar, account = get_credentials({"user": doc.owner or frappe.session.user}) @@ -299,11 +300,11 @@ def google_calendar_insert_events(doc, method=None): try: _google_calendar_insert_events(google_calendar, account, event, doc) except HttpError as err: - if err.resp.status in [400, 404, 410]: + if err.resp.status in [400, 404, 410, 500, 503]: time.sleep(2) _google_calendar_insert_events(google_calendar, account, event, doc) else: - frappe.log_error(err, _("Google Calendar - Could not insert event in Google Calendar.")) + frappe.throw(_("Google Calendar - Could not insert event in Google Calendar {0}."),format(account.name)) def google_calendar_update_events(doc, method=None): @@ -321,12 +322,9 @@ def google_calendar_update_events(doc, method=None): google_calendar.events().update(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id, body=event).execute() - if not frappe.db.exists("Google Calendar", {"user": doc.owner or frappe.session.user}): - return - # Workaround to avoid triggering updation when Event is being inserted since # creation and modified are same when inserting doc - if doc.modified == doc.creation: + if not frappe.db.exists("Google Calendar", {"user": doc.owner or frappe.session.user}) or doc.modified == doc.creation: return google_calendar, account = get_credentials({"user": doc.owner or frappe.session.user}) @@ -337,11 +335,11 @@ def google_calendar_update_events(doc, method=None): try: _google_calendar_update_events(google_calendar, account, doc) except HttpError as err: - if err.resp.status in [400, 404, 410]: + if err.resp.status in [400, 404, 410, 500, 503]: time.sleep(2) _google_calendar_update_events(google_calendar, account, doc) else: - frappe.log_error(err, "Google Calendar - Could not update Event {0} in Google Calendar.".format(doc.name)) + frappe.throw(_("Google Calendar - Could not update Event {0} in Google Calendar.").format(doc.name)) def google_calendar_delete_events(doc, method=None): # Delete Events from Google Calendar @@ -363,11 +361,11 @@ def google_calendar_delete_events(doc, method=None): try: _google_calendar_delete_events(google_calendar, account, doc) except HttpError as err: - if err.resp.status in [400, 404, 410]: + if err.resp.status in [400, 404, 410, 500, 503]: time.sleep(2) _google_calendar_delete_events(google_calendar, account, doc) else: - frappe.log_error(err, "Google Calendar - Could not delete Event {0} from Google Calendar.".format(doc.name)) + frappe.throw(_("Google Calendar - Could not delete Event {0} from Google Calendar.").format(doc.name)) def google_calendar_to_repeat_on(start, end, recurrence=None): """ @@ -576,3 +574,4 @@ def get_recurrence_parameter(recurrence): - Monthly Event: ['RRULE:FREQ=MONTHLY;BYDAY=4TH'] - Yearly Event: ['RRULE:FREQ=YEARLY;'] - Custom Event: ['RRULE:FREQ=WEEKLY;WKST=SU;UNTIL=20191028;BYDAY=MO,WE'] +""" \ No newline at end of file From 7ea479f2ef4993bd0207b1010bcc5b4f11a00131 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 31 Jul 2019 14:51:19 +0530 Subject: [PATCH 129/203] fix: scheduling for all day event --- .../google_calendar/google_calendar.py | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 6071666d32..c32aeb6606 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -248,10 +248,12 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): # If Google Calendar Event if confirmed, then create an Event if event.get("status") == "confirmed": - try: - recurrence = event.get('recurrence')[0] - except IndexError: - recurrence = None + recurrence = None + if event.get('recurrence'): + try: + recurrence = event.get('recurrence')[0] + except IndexError: + pass calendar_event = { "doctype": "Event", @@ -292,7 +294,7 @@ def google_calendar_insert_events(doc, method=None): "description": doc.description, "google_calendar_event": 1 } - event.update(google_calendar_format_date(get_datetime(doc.starts_on), get_datetime(doc.ends_on))) + event.update(google_calendar_format_date(doc.all_day, get_datetime(doc.starts_on), get_datetime(doc.ends_on))) if doc.repeat_on: event.update({"recurrence": unparse_recurrence(doc)}) @@ -315,7 +317,7 @@ def google_calendar_update_events(doc, method=None): event["summary"] = doc.subject event["description"] = doc.description event["recurrence"] = unparse_recurrence(doc) - event.update(google_calendar_format_date(get_datetime(doc.starts_on), get_datetime(doc.ends_on))) + event.update(google_calendar_format_date(doc.all_day, get_datetime(doc.starts_on), get_datetime(doc.ends_on))) if doc.event_type == "Cancelled" or doc.status == "Closed": event["status"] = "cancelled" @@ -410,7 +412,7 @@ def google_calendar_to_repeat_on(start, end, recurrence=None): byday = byday.split("=")[1] repeat_day_week_number, repeat_day_name = None, None - for num in ["1", "2", "3", "4", "5"]: + for num in ["-2", "-1", "1", "2", "3", "4", "5"]: if num in byday: repeat_day_week_number = num break @@ -459,6 +461,12 @@ def google_calendar_format_date(all_day, starts_on, ends_on=None): def parse_recurrence(repeat_day_week_number, repeat_day_name): # Returns (repeat_on) exact date for combination eg 4TH viz. 4th thursday of a month + + if repeat_day_week_number < 0: + # Consider a month with 5 weeks and event is to be repeated in last week of every month, google caledar considers + # a month has 4 weeks and hence itll return -1 for a month with 5 weeks. + repeat_day_week_number = 4 + weekdays = get_weekdays() current_date = now_datetime() isset_day_name, isset_day_number = False, False @@ -572,6 +580,7 @@ def get_recurrence_parameter(recurrence): - Daily Event: ['RRULE:FREQ=DAILY'] - Weekly Event: ['RRULE:FREQ=WEEKLY;BYDAY=MO,TU,TH'] - Monthly Event: ['RRULE:FREQ=MONTHLY;BYDAY=4TH'] + - BYDAY: -2, -1, 1, 2, 3, 4 with weekdays (-2 edge case for April 2017 had 6 weeks in a month) - Yearly Event: ['RRULE:FREQ=YEARLY;'] - Custom Event: ['RRULE:FREQ=WEEKLY;WKST=SU;UNTIL=20191028;BYDAY=MO,WE'] """ \ No newline at end of file From c708e2562cdd4a38a6c0f1412a4aa84705eef990 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 31 Jul 2019 15:40:32 +0530 Subject: [PATCH 130/203] fix: update existing event --- .../google_calendar/google_calendar.py | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index c32aeb6606..b4403b5899 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -212,6 +212,25 @@ def check_remote_calendar(account, google_calendar): def google_calendar_get_events(g_calendar, method=None, page_length=10): # Get Events from Google Calendar + def _insert_event(account, event, recurrence=None): + calendar_event = { + "doctype": "Event", + "subject": event.get("summary"), + "description": event.get("description"), + "google_calendar_event": 1, + "google_calendar_id": account.google_calendar_id, + "google_calendar_event_id": event.get("id"), + "synced_from_google_calendar": 1 + } + calendar_event.update(google_calendar_to_repeat_on(recurrence=recurrence, start=event.get('start'), end=event.get('end'))) + frappe.get_doc(calendar_event).insert(ignore_permissions=True) + + def _update_event(account, event, recurrence=None): + calendar_event = frappe.get_doc("Event", {"google_calendar_event_id": event.get("id")}) + calendar_event.subject = event.get("summary") + calendar_event.description = event.get("description") + calendar_event.update(google_calendar_to_repeat_on(recurrence=recurrence, start=event.get('start'), end=event.get('end'))) + calendar_event.save(ignore_permissions=True) google_calendar, account = get_credentials({"name": g_calendar}) @@ -219,7 +238,6 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): return results = [] - while True: try: # API Response listed at EOF @@ -255,17 +273,10 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): except IndexError: pass - calendar_event = { - "doctype": "Event", - "subject": event.get("summary"), - "description": event.get("description"), - "google_calendar_event": 1, - "google_calendar_id": account.google_calendar_id, - "google_calendar_event_id": event.get("id"), - "synced_from_google_calendar": 1 - } - calendar_event.update(google_calendar_to_repeat_on(recurrence=recurrence, start=event.get('start'), end=event.get('end'))) - frappe.get_doc(calendar_event).insert(ignore_permissions=True) + if not frappe.db.exists("Event", {"google_calendar_event_id": event.get("id")}): + _insert_event(account, event, recurrence) + else: + _update_event(account, event, recurrence) elif event.get("status") == "cancelled": # If any synced Google Calendar Event is cancelled, then close the Event frappe.db.set_value("Event", {"google_calendar_id": account.google_calendar_id, "google_calendar_event_id": event.get("id")}, "status", "Closed") From 24eaa9392c2ae523a1339bf27d976f9095508499 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 31 Jul 2019 16:22:37 +0530 Subject: [PATCH 131/203] fix: codacy --- .../integrations/doctype/google_calendar/google_calendar.js | 2 +- .../integrations/doctype/google_calendar/google_calendar.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.js b/frappe/integrations/doctype/google_calendar/google_calendar.js index e0dce4d4ba..b85d5b94f6 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.js +++ b/frappe/integrations/doctype/google_calendar/google_calendar.js @@ -26,7 +26,7 @@ frappe.ui.form.on('Google Calendar', { args: { "g_contact": frm.doc.name }, - }).then((r) => { + }).then(() => { frappe.hide_progress(); frappe.msgprint("Google Calendar Events Synced"); }); diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index b4403b5899..69139c03a9 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -541,8 +541,7 @@ def get_recurrence_parameter(recurrence): return frequency, until, byday -""" - API Response +"""API Response { 'kind': 'calendar#events', 'etag': '"etag"', @@ -593,5 +592,4 @@ def get_recurrence_parameter(recurrence): - Monthly Event: ['RRULE:FREQ=MONTHLY;BYDAY=4TH'] - BYDAY: -2, -1, 1, 2, 3, 4 with weekdays (-2 edge case for April 2017 had 6 weeks in a month) - Yearly Event: ['RRULE:FREQ=YEARLY;'] - - Custom Event: ['RRULE:FREQ=WEEKLY;WKST=SU;UNTIL=20191028;BYDAY=MO,WE'] -""" \ No newline at end of file + - Custom Event: ['RRULE:FREQ=WEEKLY;WKST=SU;UNTIL=20191028;BYDAY=MO,WE']""" \ No newline at end of file From 8730e08eb87c5c13b472e3edc0577c432e28330a Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 31 Jul 2019 17:06:43 +0530 Subject: [PATCH 132/203] chore: code formatting --- frappe/config/integrations.py | 10 ---------- .../doctype/google_calendar/google_calendar.py | 18 +++++++++--------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/frappe/config/integrations.py b/frappe/config/integrations.py index 2bfbae48ad..bbe3966b29 100644 --- a/frappe/config/integrations.py +++ b/frappe/config/integrations.py @@ -92,16 +92,6 @@ def get_data(): "name": "Google Settings", "description": _("Google API Settings."), }, - { - "type": "doctype", - "name": "GCalendar Settings", - "description": _("Configure your google calendar integration"), - }, - { - "type": "doctype", - "name": "GCalendar Account", - "description": _("Configure accounts for google calendar"), - }, { "type": "doctype", "name": "GSuite Settings", diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 69139c03a9..12d4386195 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -222,14 +222,14 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): "google_calendar_event_id": event.get("id"), "synced_from_google_calendar": 1 } - calendar_event.update(google_calendar_to_repeat_on(recurrence=recurrence, start=event.get('start'), end=event.get('end'))) + calendar_event.update(google_calendar_to_repeat_on(recurrence=recurrence, start=event.get("start"), end=event.get("end"))) frappe.get_doc(calendar_event).insert(ignore_permissions=True) def _update_event(account, event, recurrence=None): calendar_event = frappe.get_doc("Event", {"google_calendar_event_id": event.get("id")}) calendar_event.subject = event.get("summary") calendar_event.description = event.get("description") - calendar_event.update(google_calendar_to_repeat_on(recurrence=recurrence, start=event.get('start'), end=event.get('end'))) + calendar_event.update(google_calendar_to_repeat_on(recurrence=recurrence, start=event.get("start"), end=event.get("end"))) calendar_event.save(ignore_permissions=True) google_calendar, account = get_credentials({"name": g_calendar}) @@ -262,14 +262,14 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): break for idx, event in enumerate(results): - frappe.publish_realtime('import_google_calendar', dict(progress=idx+1, total=len(results)), user=frappe.session.user) + frappe.publish_realtime("import_google_calendar", dict(progress=idx+1, total=len(results)), user=frappe.session.user) # If Google Calendar Event if confirmed, then create an Event if event.get("status") == "confirmed": recurrence = None - if event.get('recurrence'): + if event.get("recurrence"): try: - recurrence = event.get('recurrence')[0] + recurrence = event.get("recurrence")[0] except IndexError: pass @@ -411,10 +411,10 @@ def google_calendar_to_repeat_on(start, end, recurrence=None): if repeat_on["repeat_on"] == "Daily": repeat_on["ends_on"] = None - repeat_on["repeat_till"] = datetime.strptime(until, '%Y%m%d') if until else None + repeat_on["repeat_till"] = datetime.strptime(until, "%Y%m%d") if until else None if byday and repeat_on["repeat_on"] == "Weekly": - repeat_on["repeat_till"] = datetime.strptime(until, '%Y%m%d') if until else None + repeat_on["repeat_till"] = datetime.strptime(until, "%Y%m%d") if until else None byday = byday.split("=")[1].split(",") for repeat_day in byday: repeat_on[google_calendar_days[repeat_day]] = 1 @@ -437,11 +437,11 @@ def google_calendar_to_repeat_on(start, end, recurrence=None): start_date = parse_recurrence(int(repeat_day_week_number), repeat_day_name) repeat_on["starts_on"] = start_date repeat_on["ends_on"] = add_to_date(start_date, minutes=5) - repeat_on["repeat_till"] = datetime.strptime(until, '%Y%m%d') if until else None + repeat_on["repeat_till"] = datetime.strptime(until, "%Y%m%d") if until else None if repeat_on["repeat_till"] == "Yearly": repeat_on["ends_on"] = None - repeat_on["repeat_till"] = datetime.strptime(until, '%Y%m%d') if until else None + repeat_on["repeat_till"] = datetime.strptime(until, "%Y%m%d") if until else None return repeat_on From 9bd0715d73b9a99ee8d9b9a14b7f962dd4c51753 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 31 Jul 2019 17:55:50 +0530 Subject: [PATCH 133/203] fix: error handling --- .../google_calendar/google_calendar.py | 50 +++++-------------- 1 file changed, 12 insertions(+), 38 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 12d4386195..93980d5e30 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -182,36 +182,28 @@ def get_credentials(g_calendar): return google_calendar, account def check_remote_calendar(account, google_calendar): + def _create_calendar(account): calendar = { "summary": account.calendar_name, "timeZone": frappe.db.get_single_value("System Settings", "time_zone") } - try: - created_calendar = google_calendar.calendars().insert(body=calendar).execute() - frappe.db.set_value("Google Calendar", account.name, "google_calendar_id", created_calendar.get("id")) - frappe.db.commit() - except Exception: - frappe.log_error(frappe.get_traceback()) + created_calendar = google_calendar.calendars().insert(body=calendar).execute() + frappe.db.set_value("Google Calendar", account.name, "google_calendar_id", created_calendar.get("id")) + frappe.db.commit() + account.load_from_db() try: if account.google_calendar_id: - try: - account.load_from_db() - google_calendar.calendars().get(calendarId=account.google_calendar_id).execute() - except Exception: - frappe.log_error(frappe.get_traceback()) + google_calendar.calendars().get(calendarId=account.google_calendar_id).execute() else: _create_calendar(account) except HttpError as err: - if err.resp.status in [403, 404, 410, 500, 503]: - time.sleep(5) - _create_calendar(account) - else: - frappe.log_error(frappe.get_traceback(), _("Google Calendar - Could not create Calendar for {0}.").format(account.name)) + frappe.throw(_("Google Calendar - Could not create Calendar for {0}, error code {1}.").format(account.name, err.resp.status)) def google_calendar_get_events(g_calendar, method=None, page_length=10): # Get Events from Google Calendar + def _insert_event(account, event, recurrence=None): calendar_event = { "doctype": "Event", @@ -245,12 +237,7 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, singleEvents=False, showDeleted=True, syncToken=sync_token).execute() except HttpError as err: - if err.resp.status in [400, 404, 410, 500, 503]: - time.sleep(5) - events = google_calendar.events().list(calendarId=account.google_calendar_id, maxResults=page_length, - singleEvents=False, showDeleted=True, timeMin=add_years(None, -1).strftime("%Y-%m-%dT%H:%M:%SZ")).execute() - else: - frappe.throw(_("Google Calendar - Could not fetch event from Google Calendar.")) + frappe.throw(_("Google Calendar - Could not fetch event from Google Calendar, error code {0}.").format(err.resp.status)) for event in events.get("items"): results.append(event) @@ -313,12 +300,7 @@ def google_calendar_insert_events(doc, method=None): try: _google_calendar_insert_events(google_calendar, account, event, doc) except HttpError as err: - if err.resp.status in [400, 404, 410, 500, 503]: - time.sleep(2) - _google_calendar_insert_events(google_calendar, account, event, doc) - else: - frappe.throw(_("Google Calendar - Could not insert event in Google Calendar {0}."),format(account.name)) - + frappe.throw(_("Google Calendar - Could not insert event in Google Calendar {0}, error code {1}."),format(account.name, err.resp.status)) def google_calendar_update_events(doc, method=None): # Update Events with Google Calendar @@ -348,11 +330,7 @@ def google_calendar_update_events(doc, method=None): try: _google_calendar_update_events(google_calendar, account, doc) except HttpError as err: - if err.resp.status in [400, 404, 410, 500, 503]: - time.sleep(2) - _google_calendar_update_events(google_calendar, account, doc) - else: - frappe.throw(_("Google Calendar - Could not update Event {0} in Google Calendar.").format(doc.name)) + frappe.throw(_("Google Calendar - Could not update Event {0} in Google Calendar, error code {1}.").format(doc.name, err.resp.status)) def google_calendar_delete_events(doc, method=None): # Delete Events from Google Calendar @@ -374,11 +352,7 @@ def google_calendar_delete_events(doc, method=None): try: _google_calendar_delete_events(google_calendar, account, doc) except HttpError as err: - if err.resp.status in [400, 404, 410, 500, 503]: - time.sleep(2) - _google_calendar_delete_events(google_calendar, account, doc) - else: - frappe.throw(_("Google Calendar - Could not delete Event {0} from Google Calendar.").format(doc.name)) + frappe.throw(_("Google Calendar - Could not delete Event {0} from Google Calendar, error code {1}.").format(doc.name, err.resp.status)) def google_calendar_to_repeat_on(start, end, recurrence=None): """ From 4164c62efc630879a2378630ee454263084630dc Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 31 Jul 2019 20:10:39 +0530 Subject: [PATCH 134/203] fix(doctor): Show status of scheduler in bench doctor --- frappe/commands/scheduler.py | 5 ++++- frappe/utils/doctor.py | 13 ++++++++++++- frappe/utils/scheduler.py | 20 +++++++++++++------- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/frappe/commands/scheduler.py b/frappe/commands/scheduler.py index 8d1cca286d..6f51c81211 100755 --- a/frappe/commands/scheduler.py +++ b/frappe/commands/scheduler.py @@ -115,9 +115,12 @@ def set_maintenance_mode(context, state, site=None): @click.command('doctor') #Passing context always gets a site and if there is no use site it breaks @click.option('--site', help='site name') -def doctor(site=None): +@pass_context +def doctor(context, site=None): "Get diagnostic info about background workers" from frappe.utils.doctor import doctor as _doctor + if not site: + site = get_site(context) return _doctor(site=site) @click.command('show-pending-jobs') diff --git a/frappe/utils/doctor.py b/frappe/utils/doctor.py index 9f046bb8ea..e97f792b88 100644 --- a/frappe/utils/doctor.py +++ b/frappe/utils/doctor.py @@ -3,7 +3,7 @@ import frappe.utils from collections import defaultdict from rq import Worker, Connection from frappe.utils.background_jobs import get_redis_conn, get_queue, get_queue_list -from frappe.utils.scheduler import is_scheduler_disabled +from frappe.utils.scheduler import is_scheduler_disabled, is_scheduler_inactive from six import iteritems @@ -107,8 +107,19 @@ def doctor(site=None): for s in sites: frappe.init(s) frappe.connect() + if is_scheduler_disabled(): print("Scheduler disabled for", s) + + if frappe.local.conf.maintenance_mode: + print("Maintenance mode on for", s) + + if frappe.local.conf.pause_scheduler: + print("Scheduler paused for", s) + + if is_scheduler_inactive(): + print("Scheduler inactive for", s) + frappe.destroy() # TODO improve this diff --git a/frappe/utils/scheduler.py b/frappe/utils/scheduler.py index 1cf62d0cb0..605ca7c8b4 100755 --- a/frappe/utils/scheduler.py +++ b/frappe/utils/scheduler.py @@ -79,14 +79,8 @@ def enqueue_events_for_site(site, queued_jobs): try: frappe.init(site=site) - if frappe.local.conf.maintenance_mode: - return - - if frappe.local.conf.pause_scheduler: - return - frappe.connect() - if is_scheduler_disabled(): + if is_scheduler_inactive(): return enqueue_events(site=site, queued_jobs=queued_jobs) @@ -226,6 +220,18 @@ def get_enabled_scheduler_events(): return ["all", "hourly", "hourly_long", "daily", "daily_long", "weekly", "weekly_long", "monthly", "monthly_long", "cron"] +def is_scheduler_inactive(): + if frappe.local.conf.maintenance_mode: + return True + + if frappe.local.conf.pause_scheduler: + return True + + if is_scheduler_disabled(): + return True + + return False + def is_scheduler_disabled(): if frappe.conf.disable_scheduler: return True From d11159ad507259b93f597be77c18811b9319aef3 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 31 Jul 2019 20:11:54 +0530 Subject: [PATCH 135/203] fix(background-jobs): Show status of scheduler in background-jobs page --- frappe/core/page/background_jobs/background_jobs.js | 6 ++++++ frappe/core/page/background_jobs/background_jobs.py | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/frappe/core/page/background_jobs/background_jobs.js b/frappe/core/page/background_jobs/background_jobs.js index 458a8ad5ee..2bde8454a9 100644 --- a/frappe/core/page/background_jobs/background_jobs.js +++ b/frappe/core/page/background_jobs/background_jobs.js @@ -13,6 +13,12 @@ frappe.pages['background_jobs'].on_page_load = function(wrapper) { frappe.pages['background_jobs'].on_page_show = function(wrapper) { frappe.pages.background_jobs.refresh_jobs(); + frappe.call({ + method: 'frappe.core.page.background_jobs.background_jobs.get_scheduler_status', + callback: function(r) { + frappe.pages.background_jobs.page.set_indicator(...r.message); + } + }); } frappe.pages.background_jobs.refresh_jobs = function() { diff --git a/frappe/core/page/background_jobs/background_jobs.py b/frappe/core/page/background_jobs/background_jobs.py index 7fd681652a..d488ccd64a 100644 --- a/frappe/core/page/background_jobs/background_jobs.py +++ b/frappe/core/page/background_jobs/background_jobs.py @@ -7,6 +7,8 @@ import frappe from rq import Queue, Worker from frappe.utils.background_jobs import get_redis_conn from frappe.utils import format_datetime, cint +from frappe.utils.scheduler import is_scheduler_inactive +from frappe import _ colors = { 'queued': 'orange', @@ -49,3 +51,9 @@ def get_info(show_failed=False): for j in q.get_jobs()[:10]: add_job(j, q.name) return jobs + +@frappe.whitelist() +def get_scheduler_status(): + if is_scheduler_inactive(): + return [_("Inactive"), "red"] + return [_("Active"), "green"] From ce60e5c4564543a608338a04e5266a0c68133563 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 31 Jul 2019 21:50:39 +0530 Subject: [PATCH 136/203] fix: remove unused imports --- frappe/integrations/doctype/google_calendar/google_calendar.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 93980d5e30..2069a24d91 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -7,13 +7,12 @@ import frappe import requests import googleapiclient.discovery import google.oauth2.credentials -import time from frappe import _ from frappe.model.document import Document from frappe.utils import get_request_site_address from googleapiclient.errors import HttpError -from frappe.utils import add_days, add_years, get_datetime, get_weekdays, now_datetime, add_to_date, get_time_zone +from frappe.utils import add_days, get_datetime, get_weekdays, now_datetime, add_to_date, get_time_zone from dateutil import parser from datetime import datetime, timedelta From d4bf5b6d384630f53679c9c8ebf0dfbb36300702 Mon Sep 17 00:00:00 2001 From: Harshit <46772424+harshit-30@users.noreply.github.com> Date: Thu, 1 Aug 2019 12:55:58 +0530 Subject: [PATCH 137/203] fix: Change reset password button label (#7958) --- frappe/www/login.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/www/login.html b/frappe/www/login.html index d621ab0fbb..8c470ac6dd 100644 --- a/frappe/www/login.html +++ b/frappe/www/login.html @@ -87,7 +87,7 @@ - +