From 1d6b5cdecec6b27b64a4e5ac9317332ec813a4ae Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sun, 7 Jul 2019 10:07:45 +0530 Subject: [PATCH 01/74] 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 02/74] 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 03/74] 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 04/74] 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 05/74] 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 06/74] 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 07/74] 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 80000353e8dde3fa06f716c456077ede767008fe Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 24 Jul 2019 15:07:41 +0530 Subject: [PATCH 08/74] 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 00588d5597f7782955866ff1ad06913bce1777d9 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 24 Jul 2019 20:45:19 +0530 Subject: [PATCH 09/74] 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 c42a25bdd51a80acf408b49e20ae20cdb686dd50 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 25 Jul 2019 13:40:28 +0530 Subject: [PATCH 10/74] 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 b7d23e330cb5f22acd70fe0c1659a9925168c846 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 26 Jul 2019 09:54:03 +0530 Subject: [PATCH 11/74] 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 84aff14cd9a32c5378fec27e0f22199f7fc71d23 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 27 Jul 2019 15:28:35 +0530 Subject: [PATCH 12/74] 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 3d9502f150a065733d0e292f6bdd4388a5a159d6 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sun, 28 Jul 2019 11:39:04 +0530 Subject: [PATCH 13/74] 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 14/74] 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 15/74] 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 16/74] 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 17/74] 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 6a8e82be4db81df7f725a791f1f94741c0c1074d Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 29 Jul 2019 16:45:22 +0530 Subject: [PATCH 18/74] 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 83b266c36cfe44c2ca969292614a7f3e40b78f9f Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 29 Jul 2019 19:31:07 +0530 Subject: [PATCH 19/74] 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 20/74] 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 21/74] 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 cddbb005e2a23e0d008d388c75b6d783b09290c8 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 30 Jul 2019 14:45:14 +0530 Subject: [PATCH 22/74] 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 f79a44bdd68c100ce99bd66002db33fe5464c058 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 30 Jul 2019 19:52:06 +0530 Subject: [PATCH 23/74] 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 24/74] 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 03c10950388fd0909136e0e6d3ce00f61a591268 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 31 Jul 2019 10:32:40 +0530 Subject: [PATCH 25/74] 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 26/74] 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 27/74] 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 28/74] 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 29/74] 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 30/74] 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 31/74] 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 ce60e5c4564543a608338a04e5266a0c68133563 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 31 Jul 2019 21:50:39 +0530 Subject: [PATCH 32/74] 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 25463dafc42ca24a0579e9e4484b8f84970c34d5 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 1 Aug 2019 14:12:02 +0530 Subject: [PATCH 33/74] fix: ux changes --- frappe/desk/doctype/event/event.js | 9 ++++- frappe/desk/doctype/event/event.json | 39 ++++++++++--------- .../google_calendar/google_calendar.js | 20 +++++----- .../google_calendar/google_calendar.json | 11 +++--- .../google_calendar/google_calendar.py | 17 ++++---- 5 files changed, 54 insertions(+), 42 deletions(-) diff --git a/frappe/desk/doctype/event/event.js b/frappe/desk/doctype/event/event.js index a3adede558..87d78bae94 100644 --- a/frappe/desk/doctype/event/event.js +++ b/frappe/desk/doctype/event/event.js @@ -10,7 +10,14 @@ frappe.ui.form.on("Event", { "issingle": 0, } }; - }) + }); + frm.set_query('google_calendar', function() { + return { + filters: { + "owner": frappe.session.user + } + }; + }); }, refresh: function(frm) { if(frm.doc.event_participants) { diff --git a/frappe/desk/doctype/event/event.json b/frappe/desk/doctype/event/event.json index d725a107e3..fc648fcc6d 100644 --- a/frappe/desk/doctype/event/event.json +++ b/frappe/desk/doctype/event/event.json @@ -10,14 +10,16 @@ "subject", "event_category", "event_type", + "color", "send_reminder", "repeat_this_event", "column_break_4", "starts_on", "ends_on", "status", + "google_calendar", "all_day", - "google_calendar_event", + "sync_with_google_calendar", "section_break_13", "repeat_on", "repeat_till", @@ -30,8 +32,6 @@ "saturday", "sunday", "section_break_8", - "color", - "section_break_6", "description", "participants", "event_participants", @@ -189,10 +189,6 @@ "fieldtype": "Color", "label": "Color" }, - { - "fieldname": "section_break_6", - "fieldtype": "Section Break" - }, { "fieldname": "description", "fieldtype": "Text Editor", @@ -226,15 +222,16 @@ }, { "collapsible": 1, - "depends_on": "eval:doc.google_calendar_event", + "depends_on": "eval:doc.google_calendar", "fieldname": "sb_00", "fieldtype": "Section Break", "label": "Google Calendar" }, { + "fetch_from": "google_calendar.google_calendar_id", "fieldname": "google_calendar_id", "fieldtype": "Data", - "label": "Google Calendar Id", + "label": "Google Calendar ID", "read_only": 1 }, { @@ -244,14 +241,7 @@ { "fieldname": "google_calendar_event_id", "fieldtype": "Data", - "label": "Google Calendar Event Id", - "read_only": 1 - }, - { - "default": "0", - "fieldname": "google_calendar_event", - "fieldtype": "Check", - "label": "Google Calendar Event", + "label": "Google Calendar Event ID", "read_only": 1 }, { @@ -260,11 +250,24 @@ "fieldtype": "Check", "label": "Synced from Google Calendar", "read_only": 1 + }, + { + "default": "0", + "fieldname": "sync_with_google_calendar", + "fieldtype": "Check", + "label": "Sync with Google Calendar" + }, + { + "depends_on": "eval:doc.sync_with_google_calendar", + "fieldname": "google_calendar", + "fieldtype": "Link", + "label": "Google Calendar", + "options": "Google Calendar" } ], "icon": "fa fa-calendar", "idx": 1, - "modified": "2019-07-31 11:01:08.920725", + "modified": "2019-08-01 13:19:05.145607", "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 b85d5b94f6..a84db3abf3 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.js +++ b/frappe/integrations/doctype/google_calendar/google_calendar.js @@ -1,25 +1,27 @@ // Copyright (c) 2019, Frappe Technologies and contributors // For license information, please see license.txt -frappe.ui.form.on('Google Calendar', { +frappe.ui.form.on("Google Calendar", { refresh: function(frm) { - frm.set_value("user", frappe.session.user); + if (frm.is_new()) { + frm.dashboard.set_headline(__("To use Google Calendar, enable Google Settings.")); + } - frappe.realtime.on('import_google_calendar', (data) => { + 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])); + 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'); + frm.dashboard.hide_progress("Sync Google Calendar"); } } }); if (frm.doc.refresh_token) { - frm.add_custom_button(__('Sync Calendar'), function () { + frm.add_custom_button(__("Sync Calendar"), function () { frappe.show_alert({ - indicator: 'green', - message: __('Syncing') + indicator: "green", + message: __("Syncing") }); frappe.call({ method: "frappe.integrations.doctype.google_calendar.google_calendar.sync", diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.json b/frappe/integrations/doctype/google_calendar/google_calendar.json index d8fdd425ce..9474387c80 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.json +++ b/frappe/integrations/doctype/google_calendar/google_calendar.json @@ -1,5 +1,5 @@ { - "autoname": "format:Calendar-{user}", + "autoname": "format:{calendar_name}-{user}", "creation": "2019-07-06 17:54:09.450100", "doctype": "DocType", "editable_grid": 1, @@ -40,7 +40,6 @@ "in_list_view": 1, "label": "User", "options": "User", - "read_only": 1, "reqd": 1 }, { @@ -56,19 +55,19 @@ }, { "fieldname": "refresh_token", - "fieldtype": "Password", + "fieldtype": "Data", "hidden": 1, "label": "Refresh Token" }, { "fieldname": "authorization_code", - "fieldtype": "Password", + "fieldtype": "Data", "hidden": 1, "label": "Authorization Code" }, { "fieldname": "next_sync_token", - "fieldtype": "Password", + "fieldtype": "Data", "hidden": 1, "label": "Next Sync Token" }, @@ -110,7 +109,7 @@ "label": "Push to Google Calendar" } ], - "modified": "2019-07-29 21:06:29.674579", + "modified": "2019-08-01 14:11:49.438425", "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 2069a24d91..ede6c0ccc8 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -209,6 +209,7 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): "subject": event.get("summary"), "description": event.get("description"), "google_calendar_event": 1, + "google_calendar": account.google_calendar, "google_calendar_id": account.google_calendar_id, "google_calendar_event_id": event.get("id"), "synced_from_google_calendar": 1 @@ -274,14 +275,13 @@ def google_calendar_insert_events(doc, method=None): 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}) or doc.synced_from_google_calendar: + if not frappe.db.exists("Google Calendar", {"name": doc.google_calendar}) or doc.synced_from_google_calendar \ + or not doc.sync_with_google_calendar: return - google_calendar, account = get_credentials({"user": doc.owner or frappe.session.user}) + google_calendar, account = get_credentials({"name": doc.google_calendar}) if not account.push_to_google_calendar: return @@ -318,10 +318,11 @@ def google_calendar_update_events(doc, method=None): # 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": doc.owner or frappe.session.user}) or doc.modified == doc.creation: + if not frappe.db.exists("Google Calendar", {"name": doc.google_calendar}) or doc.modified == doc.creation \ + or not doc.sync_with_google_calendar: return - google_calendar, account = get_credentials({"user": doc.owner or frappe.session.user}) + google_calendar, account = get_credentials({"name": doc.google_calendar}) if not account.push_to_google_calendar: return @@ -340,10 +341,10 @@ def google_calendar_delete_events(doc, method=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}): + if not frappe.db.exists("Google Calendar", {"name": doc.google_calendar}): return - google_calendar, account = get_credentials({"user": doc.owner or frappe.session.user}) + google_calendar, account = get_credentials({"name": doc.google_calendar}) if not account.push_to_google_calendar: return From 86132e166aa2cb2ce9e256899c76ec32502329a8 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 1 Aug 2019 14:26:18 +0530 Subject: [PATCH 34/74] fix: scope --- .../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 ede6c0ccc8..81daed9bc7 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -16,7 +16,7 @@ from frappe.utils import add_days, get_datetime, get_weekdays, now_datetime, add from dateutil import parser from datetime import datetime, timedelta -SCOPES = "https://www.googleapis.com/auth/calendar/v3" +SCOPES = "https://www.googleapis.com/auth/calendar" google_calendar_frequencies = { "RRULE:FREQ=DAILY": "Daily", @@ -169,7 +169,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": SCOPES + "scopes": "https://www.googleapis.com/auth/calendar/v3" } credentials = google.oauth2.credentials.Credentials(**credentials_dict) From 8389b1d04110b7cc5d5bf02c0879efe7e1bae707 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 1 Aug 2019 14:27:23 +0530 Subject: [PATCH 35/74] fix: google calendar json --- .../doctype/google_calendar/google_calendar.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.json b/frappe/integrations/doctype/google_calendar/google_calendar.json index 9474387c80..f1fe9e921b 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.json +++ b/frappe/integrations/doctype/google_calendar/google_calendar.json @@ -56,19 +56,16 @@ { "fieldname": "refresh_token", "fieldtype": "Data", - "hidden": 1, "label": "Refresh Token" }, { "fieldname": "authorization_code", "fieldtype": "Data", - "hidden": 1, "label": "Authorization Code" }, { "fieldname": "next_sync_token", "fieldtype": "Data", - "hidden": 1, "label": "Next Sync Token" }, { @@ -109,7 +106,7 @@ "label": "Push to Google Calendar" } ], - "modified": "2019-08-01 14:11:49.438425", + "modified": "2019-08-01 14:26:47.411823", "modified_by": "Administrator", "module": "Integrations", "name": "Google Calendar", From 49dd70afc223a574eac3bcbb506bc5ee5c36a44b Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 1 Aug 2019 18:59:53 +0530 Subject: [PATCH 36/74] fix: change naming --- .../doctype/google_calendar/google_calendar.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.json b/frappe/integrations/doctype/google_calendar/google_calendar.json index f1fe9e921b..c494e9c553 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.json +++ b/frappe/integrations/doctype/google_calendar/google_calendar.json @@ -1,5 +1,5 @@ { - "autoname": "format:{calendar_name}-{user}", + "autoname": "field:calendar_name", "creation": "2019-07-06 17:54:09.450100", "doctype": "DocType", "editable_grid": 1, @@ -47,7 +47,8 @@ "fieldname": "calendar_name", "fieldtype": "Data", "label": "Calendar Name", - "reqd": 1 + "reqd": 1, + "unique": 1 }, { "fieldname": "section_break_3", @@ -106,7 +107,7 @@ "label": "Push to Google Calendar" } ], - "modified": "2019-08-01 14:26:47.411823", + "modified": "2019-08-01 18:59:36.943325", "modified_by": "Administrator", "module": "Integrations", "name": "Google Calendar", From 514d0d38a6ac50b8f4182771eb9088de2f2fe74e Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 1 Aug 2019 23:19:00 +0530 Subject: [PATCH 37/74] fix: miscellaneous fixes --- frappe/desk/doctype/event/event.py | 3 + .../google_calendar/google_calendar.js | 2 +- .../google_calendar/google_calendar.json | 12 ++-- .../google_calendar/google_calendar.py | 72 ++++++++++--------- 4 files changed, 50 insertions(+), 39 deletions(-) diff --git a/frappe/desk/doctype/event/event.py b/frappe/desk/doctype/event/event.py index 54d938716e..43ad50305f 100644 --- a/frappe/desk/doctype/event/event.py +++ b/frappe/desk/doctype/event/event.py @@ -31,6 +31,9 @@ class Event(Document): 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.")) + if self.sync_with_google_calendar and not self.google_calendar: + frappe.throw(_("Select Google Calendar to which event should be synced.")) + def on_update(self): self.sync_communication() diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.js b/frappe/integrations/doctype/google_calendar/google_calendar.js index a84db3abf3..efe71f8ec5 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", { frappe.call({ method: "frappe.integrations.doctype.google_calendar.google_calendar.sync", args: { - "g_contact": frm.doc.name + "g_calendar": frm.doc.name }, }).then(() => { frappe.hide_progress(); diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.json b/frappe/integrations/doctype/google_calendar/google_calendar.json index c494e9c553..b6cc545d3e 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.json +++ b/frappe/integrations/doctype/google_calendar/google_calendar.json @@ -46,6 +46,7 @@ "description": "The name that will appear in Google Calendar", "fieldname": "calendar_name", "fieldtype": "Data", + "in_list_view": 1, "label": "Calendar Name", "reqd": 1, "unique": 1 @@ -56,17 +57,20 @@ }, { "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": "next_sync_token", - "fieldtype": "Data", + "fieldtype": "Password", + "hidden": 1, "label": "Next Sync Token" }, { @@ -107,7 +111,7 @@ "label": "Push to Google Calendar" } ], - "modified": "2019-08-01 18:59:36.943325", + "modified": "2019-08-01 23:13:18.877561", "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 81daed9bc7..fb84787484 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -98,7 +98,7 @@ def validate_google_settings(): 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 Calendar Name is set to flags to set_value after Authorization Code is obtained. """ google_settings = frappe.get_doc("Google Settings") @@ -203,28 +203,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 - 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": account.google_calendar, - "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}) + google_calendar, account = get_credentials(g_calendar) if not account.pull_from_google_calendar: return @@ -261,27 +240,52 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): pass if not frappe.db.exists("Event", {"google_calendar_event_id": event.get("id")}): - _insert_event(account, event, recurrence) + insert_event_to_calendar(account, event, recurrence) else: - _update_event(account, event, recurrence) + update_event_in_calendar(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") else: pass +def insert_event_to_calendar(account, event, recurrence=None): + # Inserts new Event in Frappe Calendar + + calendar_event = { + "doctype": "Event", + "subject": event.get("summary"), + "description": event.get("description"), + "google_calendar_event": 1, + "google_calendar": account.name, + "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_in_calendar(account, event, recurrence=None): + # Updates Event in Frappe Calendar + + 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) + def google_calendar_insert_events(doc, method=None): # 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() + event = google_calendar.events().insert(calendarId=doc.google_calendar_id, body=event).execute() frappe.db.set_value("Event", doc.name, "google_calendar_event_id", event.get("id"), update_modified=False) if not frappe.db.exists("Google Calendar", {"name": doc.google_calendar}) or doc.synced_from_google_calendar \ or not doc.sync_with_google_calendar: return - google_calendar, account = get_credentials({"name": doc.google_calendar}) + google_calendar, account = get_credentials(doc.google_calendar) if not account.push_to_google_calendar: return @@ -299,13 +303,13 @@ def google_calendar_insert_events(doc, method=None): try: _google_calendar_insert_events(google_calendar, account, event, doc) except HttpError as err: - frappe.throw(_("Google Calendar - Could not insert event in Google Calendar {0}, error code {1}."),format(account.name, err.resp.status)) + 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 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 = google_calendar.events().get(calendarId=doc.google_calendar_id, eventId=doc.google_calendar_event_id).execute() event["summary"] = doc.subject event["description"] = doc.description event["recurrence"] = unparse_recurrence(doc) @@ -314,7 +318,7 @@ def google_calendar_update_events(doc, method=None): 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() + google_calendar.events().update(calendarId=doc.google_calendar_id, eventId=doc.google_calendar_event_id, body=event).execute() # Workaround to avoid triggering updation when Event is being inserted since # creation and modified are same when inserting doc @@ -322,7 +326,7 @@ def google_calendar_update_events(doc, method=None): or not doc.sync_with_google_calendar: return - google_calendar, account = get_credentials({"name": doc.google_calendar}) + google_calendar, account = get_credentials(doc.google_calendar) if not account.push_to_google_calendar: return @@ -336,15 +340,15 @@ def google_calendar_delete_events(doc, method=None): # Delete Events from Google Calendar def _google_calendar_delete_events(google_calendar, account, doc): - event = google_calendar.events().get(calendarId=account.google_calendar_id, eventId=doc.google_calendar_event_id).execute() + event = google_calendar.events().get(calendarId=doc.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() + google_calendar.events().update(calendarId=doc.google_calendar_id, eventId=doc.google_calendar_event_id, body=event).execute() if not frappe.db.exists("Google Calendar", {"name": doc.google_calendar}): return - google_calendar, account = get_credentials({"name": doc.google_calendar}) + google_calendar, account = get_credentials(doc.google_calendar) if not account.push_to_google_calendar: return From 81e0b8b1b865df70dc62ce0d4bf9288e7d64b20a Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 2 Aug 2019 10:21:50 +0530 Subject: [PATCH 38/74] fix: test cases --- frappe/core/doctype/version/test_version.py | 2 +- .../google_settings/google_settings.json | 23 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/frappe/core/doctype/version/test_version.py b/frappe/core/doctype/version/test_version.py index d265b1ae00..df9a1e6dec 100644 --- a/frappe/core/doctype/version/test_version.py +++ b/frappe/core/doctype/version/test_version.py @@ -26,7 +26,7 @@ class TestVersion(unittest.TestCase): diff = get_diff(old_doc, new_doc)['changed'] - self.assertEqual(get_fieldnames(diff)[0], 'starts_on') + self.assertEqual(get_fieldnames(diff)[0], 'color') self.assertEqual(get_old_values(diff)[0], '01-01-2014 00:00:00') self.assertEqual(get_new_values(diff)[0], '07-20-2017 00:00:00') diff --git a/frappe/integrations/doctype/google_settings/google_settings.json b/frappe/integrations/doctype/google_settings/google_settings.json index 8a316fd67e..be157b3280 100644 --- a/frappe/integrations/doctype/google_settings/google_settings.json +++ b/frappe/integrations/doctype/google_settings/google_settings.json @@ -4,9 +4,10 @@ "engine": "InnoDB", "field_order": [ "enable", - "google_credentials", + "sb_00", "client_id", "client_secret", + "sb_01", "api_key" ], "fields": [ @@ -16,12 +17,6 @@ "fieldtype": "Check", "label": "Enable" }, - { - "depends_on": "enable", - "fieldname": "google_credentials", - "fieldtype": "Section Break", - "label": "Google Credentials" - }, { "fieldname": "client_id", "fieldtype": "Data", @@ -38,10 +33,22 @@ "fieldname": "api_key", "fieldtype": "Data", "label": "API Key" + }, + { + "depends_on": "enable", + "fieldname": "sb_00", + "fieldtype": "Section Break", + "label": "OAuth Client ID" + }, + { + "depends_on": "enable", + "fieldname": "sb_01", + "fieldtype": "Section Break", + "label": "API Key" } ], "issingle": 1, - "modified": "2019-06-29 13:26:33.201060", + "modified": "2019-08-02 10:01:11.258054", "modified_by": "Administrator", "module": "Integrations", "name": "Google Settings", From af08ee8cf13e8a84e8ed2736a6e7e73da18c6f0b Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 2 Aug 2019 10:47:16 +0530 Subject: [PATCH 39/74] chore: code cleanup --- .../google_calendar/google_calendar.py | 58 ++++++++----------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index fb84787484..864fbffd54 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -182,21 +182,19 @@ def get_credentials(g_calendar): 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") - } - 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: google_calendar.calendars().get(calendarId=account.google_calendar_id).execute() else: - _create_calendar(account) + # If no Calendar ID create a new Calendar + calendar = { + "summary": account.calendar_name, + "timeZone": frappe.db.get_single_value("System Settings", "time_zone") + } + 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 HttpError as err: frappe.throw(_("Google Calendar - Could not create Calendar for {0}, error code {1}.").format(account.name, err.resp.status)) @@ -277,10 +275,6 @@ def update_event_in_calendar(account, event, recurrence=None): def google_calendar_insert_events(doc, method=None): # Insert Events to Google Calendar - def _google_calendar_insert_events(google_calendar, account, event, doc): - event = google_calendar.events().insert(calendarId=doc.google_calendar_id, body=event).execute() - frappe.db.set_value("Event", doc.name, "google_calendar_event_id", event.get("id"), update_modified=False) - if not frappe.db.exists("Google Calendar", {"name": doc.google_calendar}) or doc.synced_from_google_calendar \ or not doc.sync_with_google_calendar: return @@ -301,25 +295,14 @@ def google_calendar_insert_events(doc, method=None): event.update({"recurrence": unparse_recurrence(doc)}) try: - _google_calendar_insert_events(google_calendar, account, event, doc) + event = google_calendar.events().insert(calendarId=doc.google_calendar_id, body=event).execute() + frappe.db.set_value("Event", doc.name, "google_calendar_event_id", event.get("id"), update_modified=False) except HttpError as err: 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 - def _google_calendar_update_events(google_calendar, account, doc): - event = google_calendar.events().get(calendarId=doc.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(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" - - google_calendar.events().update(calendarId=doc.google_calendar_id, eventId=doc.google_calendar_event_id, body=event).execute() - # 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", {"name": doc.google_calendar}) or doc.modified == doc.creation \ @@ -332,19 +315,20 @@ def google_calendar_update_events(doc, method=None): return try: - _google_calendar_update_events(google_calendar, account, doc) + event = google_calendar.events().get(calendarId=doc.google_calendar_id, eventId=doc.google_calendar_event_id).execute() + event["summary"] = doc.subject + event["description"] = doc.description + event["recurrence"] = unparse_recurrence(doc) + event["status"] = "cancelled" if doc.event_type == "Cancelled" or doc.status == "Closed" else event.get("status") + event.update(google_calendar_format_date(doc.all_day, get_datetime(doc.starts_on), get_datetime(doc.ends_on))) + + google_calendar.events().update(calendarId=doc.google_calendar_id, eventId=doc.google_calendar_event_id, body=event).execute() except HttpError as err: 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 - def _google_calendar_delete_events(google_calendar, account, doc): - event = google_calendar.events().get(calendarId=doc.google_calendar_id, eventId=doc.google_calendar_event_id).execute() - event["recurrence"] = None - event["status"] = "cancelled" - google_calendar.events().update(calendarId=doc.google_calendar_id, eventId=doc.google_calendar_event_id, body=event).execute() - if not frappe.db.exists("Google Calendar", {"name": doc.google_calendar}): return @@ -354,7 +338,11 @@ def google_calendar_delete_events(doc, method=None): return try: - _google_calendar_delete_events(google_calendar, account, doc) + event = google_calendar.events().get(calendarId=doc.google_calendar_id, eventId=doc.google_calendar_event_id).execute() + event["recurrence"] = None + event["status"] = "cancelled" + + google_calendar.events().update(calendarId=doc.google_calendar_id, eventId=doc.google_calendar_event_id, body=event).execute() except HttpError as err: frappe.throw(_("Google Calendar - Could not delete Event {0} from Google Calendar, error code {1}.").format(doc.name, err.resp.status)) From 5d5dcbb48b606b9f292169eefe20700db76706fd Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 2 Aug 2019 10:58:45 +0530 Subject: [PATCH 40/74] test: fix versioning tests --- frappe/core/doctype/version/test_version.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/version/test_version.py b/frappe/core/doctype/version/test_version.py index df9a1e6dec..20c30edbea 100644 --- a/frappe/core/doctype/version/test_version.py +++ b/frappe/core/doctype/version/test_version.py @@ -26,9 +26,9 @@ class TestVersion(unittest.TestCase): diff = get_diff(old_doc, new_doc)['changed'] - self.assertEqual(get_fieldnames(diff)[0], 'color') - self.assertEqual(get_old_values(diff)[0], '01-01-2014 00:00:00') - self.assertEqual(get_new_values(diff)[0], '07-20-2017 00:00:00') + self.assertEqual(get_fieldnames(diff)[1], 'starts_on') + self.assertEqual(get_old_values(diff)[1], '01-01-2014 00:00:00') + self.assertEqual(get_new_values(diff)[1], '20-07-2017 00:00:00') def get_fieldnames(change_array): return [d[0] for d in change_array] From 1e92c9bbdd0e4d1b7294178924128a14c13a0093 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 2 Aug 2019 11:41:37 +0530 Subject: [PATCH 41/74] fix: do not set reqd for client id and secret --- .../integrations/doctype/google_settings/google_settings.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frappe/integrations/doctype/google_settings/google_settings.js b/frappe/integrations/doctype/google_settings/google_settings.js index 488cb05b9b..0d2ede9b9b 100644 --- a/frappe/integrations/doctype/google_settings/google_settings.js +++ b/frappe/integrations/doctype/google_settings/google_settings.js @@ -2,8 +2,6 @@ // For license information, please see license.txt frappe.ui.form.on('Google Settings', { - enable: function(frm) { - frm.set_df_property('client_id', 'reqd', frm.doc.enable ? 1 : 0); - frm.set_df_property('client_secret', 'reqd', frm.doc.enable ? 1 : 0); - } + // refresh: function(frm) { + // } }); From 3e3c2e02a03dc802f24215578342805aebc05363 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 2 Aug 2019 12:15:13 +0530 Subject: [PATCH 42/74] test: fix test cases --- frappe/core/doctype/data_import/test_data_import.py | 3 ++- frappe/core/doctype/version/test_version.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/data_import/test_data_import.py b/frappe/core/doctype/data_import/test_data_import.py index b0b41a1ced..06cd64f818 100644 --- a/frappe/core/doctype/data_import/test_data_import.py +++ b/frappe/core/doctype/data_import/test_data_import.py @@ -95,6 +95,7 @@ class TestDataImport(unittest.TestCase): exporter.export_data("Event", all_doctypes=True, template=True, file_type="Excel") from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file content = read_xlsx_file_from_attached_file(fcontent=frappe.response.filecontent) - content.append(["", "_test", "Private", "05-11-2017 13:51:48", "Event", "0", "0", "", "1", "", "", 0, 0, 0, 0, 0, 0, 0, "blue"]) + content.append(["", "_test", "Private", "05-11-2017 13:51:48", "Event", "blue", "0", "0", "", "Open", "", "1", 0, "", "", 0, 0, 0, 0, 0, 0, 0]) importer.upload(content) + print(frappe.get_list("Event")) self.assertTrue(frappe.db.get_value("Event", {"subject": "_test"}, "name")) \ No newline at end of file diff --git a/frappe/core/doctype/version/test_version.py b/frappe/core/doctype/version/test_version.py index 20c30edbea..97aa69fd9c 100644 --- a/frappe/core/doctype/version/test_version.py +++ b/frappe/core/doctype/version/test_version.py @@ -28,7 +28,7 @@ class TestVersion(unittest.TestCase): self.assertEqual(get_fieldnames(diff)[1], 'starts_on') self.assertEqual(get_old_values(diff)[1], '01-01-2014 00:00:00') - self.assertEqual(get_new_values(diff)[1], '20-07-2017 00:00:00') + self.assertEqual(get_new_values(diff)[1], '07-20-2017 00:00:00') def get_fieldnames(change_array): return [d[0] for d in change_array] From 6e4000559a680fbd919d4a92f4fa963d167e477f Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 6 Aug 2019 22:38:31 +0530 Subject: [PATCH 43/74] chore: code cleanup --- frappe/hooks.py | 6 +- .../google_calendar/google_calendar.py | 125 ++++++++++-------- .../google_settings/google_settings.json | 3 +- 3 files changed, 77 insertions(+), 57 deletions(-) diff --git a/frappe/hooks.py b/frappe/hooks.py index d53704738d..0b0e03ff61 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.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", + "after_insert": "frappe.integrations.doctype.google_calendar.google_calendar.insert_event_in_google_calendar", + "on_update": "frappe.integrations.doctype.google_calendar.google_calendar.update_event_in_google_calendar", + "on_trash": "frappe.integrations.doctype.google_calendar.google_calendar.delete_event_from_google_calendar", } } diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 864fbffd54..aaf9f118cf 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -57,10 +57,6 @@ class GoogleCalendar(Document): def validate(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 = validate_google_settings() @@ -100,7 +96,6 @@ def authorize_access(g_calendar, reauthorize=None): If no Authorization code get it from Google and then request for Refresh Token. Google Calendar 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) @@ -108,7 +103,7 @@ def authorize_access(g_calendar, reauthorize=None): 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) + return get_authentication_url(client_id=google_settings.client_id, redirect_uri=redirect_uri) else: try: data = { @@ -131,21 +126,21 @@ def authorize_access(g_calendar, reauthorize=None): except Exception as e: frappe.throw(e) +def get_authentication_url(client_id=None, redirect_uri=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) + } + @frappe.whitelist() -def google_callback(client_id=None, redirect_uri=None, code=None): +def google_callback(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() + 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) + authorize_access(google_calendar) @frappe.whitelist() def sync(g_calendar=None): @@ -157,9 +152,12 @@ def sync(g_calendar=None): google_calendars = frappe.get_list("Google Calendar", filters=filters) for g in google_calendars: - google_calendar_get_events(g.name) + sync_events_from_google_calendar(g.name) -def get_credentials(g_calendar): +def get_google_calendar_object(g_calendar): + """ + Returns an object of Google Calendar along with Google Calendar doc. + """ google_settings = frappe.get_doc("Google Settings") account = frappe.get_doc("Google Calendar", g_calendar) @@ -175,13 +173,16 @@ def get_credentials(g_calendar): credentials = google.oauth2.credentials.Credentials(**credentials_dict) google_calendar = googleapiclient.discovery.build("calendar", "v3", credentials=credentials) - check_remote_calendar(account, google_calendar) + check_google_calendar(account, google_calendar) account.load_from_db() return google_calendar, account -def check_remote_calendar(account, google_calendar): - +def check_google_calendar(account, google_calendar): + """ + Checks if Google Calendar is present with the specified name. + If not, creates one. + """ account.load_from_db() try: if account.google_calendar_id: @@ -198,10 +199,14 @@ def check_remote_calendar(account, google_calendar): except HttpError as err: 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 - - google_calendar, account = get_credentials(g_calendar) +def sync_events_from_google_calendar(g_calendar, method=None, page_length=10): + """ + Syncs Events from Google Calendar in Framework Calendar. + Google Calendar returns nextSyncToken when all the events in Google Calendar are fetched. + nextSyncToken is returned at the very last page + https://developers.google.com/calendar/v3/sync + """ + google_calendar, account = get_google_calendar_object(g_calendar) if not account.pull_from_google_calendar: return @@ -248,8 +253,9 @@ def google_calendar_get_events(g_calendar, method=None, page_length=10): pass def insert_event_to_calendar(account, event, recurrence=None): - # Inserts new Event in Frappe Calendar - + """ + Inserts event in Frappe Calendar during Sync + """ calendar_event = { "doctype": "Event", "subject": event.get("summary"), @@ -264,22 +270,24 @@ def insert_event_to_calendar(account, event, recurrence=None): frappe.get_doc(calendar_event).insert(ignore_permissions=True) def update_event_in_calendar(account, event, recurrence=None): - # Updates Event in Frappe Calendar - + """ + Updates Event in Frappe Calendar if any existing Google Calendar Event is updated + """ 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) -def google_calendar_insert_events(doc, method=None): - # Insert Events to Google Calendar - +def insert_event_in_google_calendar(doc, method=None): + """ + Insert Events in Google Calendar if sync_with_google_calendar is checked. + """ if not frappe.db.exists("Google Calendar", {"name": doc.google_calendar}) or doc.synced_from_google_calendar \ or not doc.sync_with_google_calendar: return - google_calendar, account = get_credentials(doc.google_calendar) + google_calendar, account = get_google_calendar_object(doc.google_calendar) if not account.push_to_google_calendar: return @@ -289,10 +297,10 @@ def google_calendar_insert_events(doc, method=None): "description": doc.description, "google_calendar_event": 1 } - event.update(google_calendar_format_date(doc.all_day, get_datetime(doc.starts_on), get_datetime(doc.ends_on))) + event.update(format_date_according_to_google_calendar(doc.all_day, get_datetime(doc.starts_on), get_datetime(doc.ends_on))) if doc.repeat_on: - event.update({"recurrence": unparse_recurrence(doc)}) + event.update({"recurrence": repeat_on_to_google_calendar_recurrence_rule(doc)}) try: event = google_calendar.events().insert(calendarId=doc.google_calendar_id, body=event).execute() @@ -300,16 +308,22 @@ def google_calendar_insert_events(doc, method=None): except HttpError as err: 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 - +def update_event_in_google_calendar(doc, method=None): + """ + Updates Events in Google Calendar if any existing event is modified in Frappe 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", {"name": doc.google_calendar}) or doc.modified == doc.creation \ or not doc.sync_with_google_calendar: return - google_calendar, account = get_credentials(doc.google_calendar) + if doc.sync_with_google_calendar and not doc.google_calendar_event_id: + # If sync_with_google_calendar is checked later, then insert the event rather than updating it. + insert_event_in_google_calendar(doc) + return + + google_calendar, account = get_google_calendar_object(doc.google_calendar) if not account.push_to_google_calendar: return @@ -318,21 +332,23 @@ def google_calendar_update_events(doc, method=None): event = google_calendar.events().get(calendarId=doc.google_calendar_id, eventId=doc.google_calendar_event_id).execute() event["summary"] = doc.subject event["description"] = doc.description - event["recurrence"] = unparse_recurrence(doc) + event["recurrence"] = repeat_on_to_google_calendar_recurrence_rule(doc) event["status"] = "cancelled" if doc.event_type == "Cancelled" or doc.status == "Closed" else event.get("status") - event.update(google_calendar_format_date(doc.all_day, get_datetime(doc.starts_on), get_datetime(doc.ends_on))) + event.update(format_date_according_to_google_calendar(doc.all_day, get_datetime(doc.starts_on), get_datetime(doc.ends_on))) google_calendar.events().update(calendarId=doc.google_calendar_id, eventId=doc.google_calendar_event_id, body=event).execute() except HttpError as err: 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 +def delete_event_from_google_calendar(doc, method=None): + """ + Delete Events from Google Calendar if Frappe Event is deleted. + """ if not frappe.db.exists("Google Calendar", {"name": doc.google_calendar}): return - google_calendar, account = get_credentials(doc.google_calendar) + google_calendar, account = get_google_calendar_object(doc.google_calendar) if not account.push_to_google_calendar: return @@ -372,7 +388,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, byday = BYDAY=MO,TU,TH, until = 20191028 - google_calendar_frequency, until, byday = get_recurrence_parameter(recurrence) + google_calendar_frequency, until, byday = get_recurrence_parameters(recurrence) repeat_on["repeat_on"] = google_calendar_frequencies.get(google_calendar_frequency) if repeat_on["repeat_on"] == "Daily": @@ -400,7 +416,7 @@ def google_calendar_to_repeat_on(start, end, recurrence=None): break # Only Set starts_on for the event to repeat monthly - start_date = parse_recurrence(int(repeat_day_week_number), repeat_day_name) + start_date = parse_google_calendar_recurrence_rule(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 @@ -411,7 +427,7 @@ def google_calendar_to_repeat_on(start, end, recurrence=None): return repeat_on -def google_calendar_format_date(all_day, starts_on, ends_on=None): +def format_date_according_to_google_calendar(all_day, starts_on, ends_on=None): if not ends_on: ends_on = starts_on + timedelta(minutes=10) @@ -436,9 +452,10 @@ def google_calendar_format_date(all_day, starts_on, ends_on=None): return date_format -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 - +def parse_google_calendar_recurrence_rule(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. @@ -463,8 +480,10 @@ def parse_recurrence(repeat_day_week_number, repeat_day_name): return current_date -def unparse_recurrence(doc): - # Returns recurrence in Google Calendar format +def repeat_on_to_google_calendar_recurrence_rule(doc): + """ + Returns event (repeat_on) in Google Calendar format ie RRULE:FREQ=WEEKLY;BYDAY=MO,TU,TH + """ recurrence = framework_frequencies.get(doc.repeat_on) weekdays = get_weekdays() @@ -491,7 +510,7 @@ def get_week_number(dt): return int(ceil(adjusted_dom/7.0)) -def get_recurrence_parameter(recurrence): +def get_recurrence_parameters(recurrence): recurrence = recurrence.split(";") frequency, until, byday = None, None, None diff --git a/frappe/integrations/doctype/google_settings/google_settings.json b/frappe/integrations/doctype/google_settings/google_settings.json index be157b3280..086c56c020 100644 --- a/frappe/integrations/doctype/google_settings/google_settings.json +++ b/frappe/integrations/doctype/google_settings/google_settings.json @@ -30,6 +30,7 @@ "label": "Client Secret" }, { + "description": "Used For Google Maps Integration.", "fieldname": "api_key", "fieldtype": "Data", "label": "API Key" @@ -48,7 +49,7 @@ } ], "issingle": 1, - "modified": "2019-08-02 10:01:11.258054", + "modified": "2019-08-06 22:37:41.699703", "modified_by": "Administrator", "module": "Integrations", "name": "Google Settings", From bd4b809a18eb526b2f3869f56dc507272ca3811f Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 6 Aug 2019 22:51:36 +0530 Subject: [PATCH 44/74] fix: encode url while redirect --- .../integrations/doctype/google_calendar/google_calendar.js | 2 +- .../integrations/doctype/google_calendar/google_calendar.json | 4 +++- .../integrations/doctype/google_calendar/google_calendar.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.js b/frappe/integrations/doctype/google_calendar/google_calendar.js index efe71f8ec5..5bb188f91f 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.js +++ b/frappe/integrations/doctype/google_calendar/google_calendar.js @@ -12,7 +12,7 @@ frappe.ui.form.on("Google Calendar", { 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"); + frm.dashboard.hide_progress("Syncing Google Calendar"); } } }); diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.json b/frappe/integrations/doctype/google_calendar/google_calendar.json index b6cc545d3e..a7d288914f 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.json +++ b/frappe/integrations/doctype/google_calendar/google_calendar.json @@ -52,6 +52,7 @@ "unique": 1 }, { + "depends_on": "eval: doc.enable", "fieldname": "section_break_3", "fieldtype": "Section Break" }, @@ -90,6 +91,7 @@ "label": "Authorize Google Calendar Access" }, { + "depends_on": "eval: doc.enable", "fieldname": "sb_01", "fieldtype": "Section Break", "label": "Sync" @@ -111,7 +113,7 @@ "label": "Push to Google Calendar" } ], - "modified": "2019-08-01 23:13:18.877561", + "modified": "2019-08-06 22:44:22.189224", "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 aaf9f118cf..3f83c0d3e8 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -15,6 +15,7 @@ from googleapiclient.errors import HttpError 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 six.moves.urllib.parse import quote SCOPES = "https://www.googleapis.com/auth/calendar" @@ -120,7 +121,7 @@ def authorize_access(g_calendar, reauthorize=None): frappe.db.commit() frappe.local.response["type"] = "redirect" - frappe.local.response["location"] = "/desk#Form/Google%20Calendar/{}".format(google_calendar.name) + frappe.local.response["location"] = "/desk#Form/{0}/{1}".format(quote("Google Calendar"), quote(google_calendar.name)) frappe.msgprint(_("Google Calendar has been configured.")) except Exception as e: From 020dd9ef583c7a8729d1096294294810c80ac0a4 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 7 Aug 2019 15:03:02 +0530 Subject: [PATCH 45/74] fix: Headings and line breaks not seen in print formats --- frappe/templates/print_formats/standard.html | 6 +++--- frappe/templates/print_formats/standard_macros.html | 2 +- frappe/www/printview.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/frappe/templates/print_formats/standard.html b/frappe/templates/print_formats/standard.html index 43fe020b2a..a3b0819a7f 100644 --- a/frappe/templates/print_formats/standard.html +++ b/frappe/templates/print_formats/standard.html @@ -20,10 +20,10 @@ {% endif %} - {% for section in page %} + {% for section in page %}
- {%- if doc._line_breaks and loop.index != 1 -%}
{%- endif -%} - {%- if doc._show_section_headings and section.label and section.has_data -%} + {%- if doc.line_breaks and loop.index != 1 -%}
{%- endif -%} + {%- if doc.show_section_headings and section.label and section.has_data -%}

{{ _(section.label) }}

{%- endif -%} {% for column in section.columns %} diff --git a/frappe/templates/print_formats/standard_macros.html b/frappe/templates/print_formats/standard_macros.html index 2d8035ac93..d18aca3ea0 100644 --- a/frappe/templates/print_formats/standard_macros.html +++ b/frappe/templates/print_formats/standard_macros.html @@ -71,7 +71,7 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}" {%- macro render_field_with_label(df, doc) -%}
+ {%- if doc.align_labels_right %} text-right{%- endif -%}"> {% if df.fieldtype not in ("Image","HTML","Check") and doc.get(df.fieldname) != None %} diff --git a/frappe/www/printview.py b/frappe/www/printview.py index 6a3e93065d..8cbe8fbfea 100644 --- a/frappe/www/printview.py +++ b/frappe/www/printview.py @@ -95,9 +95,9 @@ def get_rendered_template(doc, name=None, print_format=None, meta=None, # determine template if print_format: - doc._show_section_headings = print_format.show_section_headings - doc._line_breaks = print_format.line_breaks - doc._align_labels_right = print_format.align_labels_right + doc.show_section_headings = print_format.show_section_headings + doc.line_breaks = print_format.line_breaks + doc.align_labels_right = print_format.align_labels_right def get_template_from_string(): return jenv.from_string(get_print_format(doc.doctype, From ff2e21ab5629dd6ae78797fd9143fac7bcc9dde5 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 7 Aug 2019 15:52:54 +0530 Subject: [PATCH 46/74] fix: Label Reference Doctype -> Reference Document Type --- frappe/social/doctype/energy_point_log/energy_point_log.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/social/doctype/energy_point_log/energy_point_log.json b/frappe/social/doctype/energy_point_log/energy_point_log.json index fa434dbdc8..627a9ef39a 100644 --- a/frappe/social/doctype/energy_point_log/energy_point_log.json +++ b/frappe/social/doctype/energy_point_log/energy_point_log.json @@ -50,7 +50,7 @@ "fieldtype": "Link", "in_list_view": 1, "in_standard_filter": 1, - "label": "Reference Doctype", + "label": "Reference Document Type", "options": "DocType", "read_only": 1, "search_index": 1 @@ -101,7 +101,7 @@ } ], "in_create": 1, - "modified": "2019-06-05 11:18:45.286367", + "modified": "2019-08-07 15:51:05.288886", "modified_by": "Administrator", "module": "Social", "name": "Energy Point Log", From 303e7a4e704f1ea5d5dbaa542b2343196bec23f5 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 7 Aug 2019 16:50:21 +0530 Subject: [PATCH 47/74] fix: Video Links in learn section --- .../frappe/views/components/ModuleLinkItem.vue | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/views/components/ModuleLinkItem.vue b/frappe/public/js/frappe/views/components/ModuleLinkItem.vue index b6ab713c97..ba1a2bbd17 100644 --- a/frappe/public/js/frappe/views/components/ModuleLinkItem.vue +++ b/frappe/public/js/frappe/views/components/ModuleLinkItem.vue @@ -6,8 +6,9 @@ {{ label || __(name) }} - {{ label || __(name) }} - + + {{ label || __(name) }} +