diff --git a/frappe/boot.py b/frappe/boot.py index 50888fcdd2..3b7e94dc81 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -76,7 +76,7 @@ def get_bootinfo(): bootinfo.calendars = sorted(frappe.get_hooks("calendars")) bootinfo.treeviews = frappe.get_hooks("treeviews") or [] bootinfo.lang_dict = get_lang_dict() - bootinfo.gsuite_enabled = get_gsuite_status() + bootinfo.google_drive_enabled = get_google_drive_status() bootinfo.success_action = get_success_action() bootinfo.update(get_email_accounts(user=frappe.session.user)) bootinfo.energy_points_enabled = is_energy_point_enabled() @@ -258,8 +258,8 @@ def get_unseen_notes(): (select user from `tabNote Seen By` nsb where nsb.parent=`tabNote`.name)''', (frappe.utils.now(), frappe.session.user), as_dict=True) -def get_gsuite_status(): - return (frappe.get_value('Gsuite Settings', None, 'enable') == '1') + def get_google_drive_status(): + return True if frappe.db.exists("Google Drive", {"enable": 1}) else False def get_success_action(): return frappe.get_all("Success Action", fields=["*"]) diff --git a/frappe/integrations/doctype/google_drive/__init__.py b/frappe/integrations/doctype/google_drive/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/integrations/doctype/google_drive/google_drive.js b/frappe/integrations/doctype/google_drive/google_drive.js new file mode 100644 index 0000000000..9bdd7ca4c5 --- /dev/null +++ b/frappe/integrations/doctype/google_drive/google_drive.js @@ -0,0 +1,30 @@ +// Copyright (c) 2019, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Google Drive', { + refresh: function(frm) { + if (frm.is_new()) { + frm.dashboard.set_headline(__("To use Google Drive, enable Google Settings.")); + } + }, + authorize_google_drive_access: function(frm) { + let reauthorize = 0; + if(frm.doc.authorization_code) { + reauthorize = 1; + } + + frappe.call({ + method: "frappe.integrations.doctype.google_drive.google_drive.authorize_access", + args: { + "g_drive": 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_drive/google_drive.json b/frappe/integrations/doctype/google_drive/google_drive.json new file mode 100644 index 0000000000..f2eb3a2f38 --- /dev/null +++ b/frappe/integrations/doctype/google_drive/google_drive.json @@ -0,0 +1,94 @@ +{ + "autoname": "format:{folder_name}-{reference_doctype}", + "creation": "2019-08-08 13:16:06.783138", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "enable", + "sb_00", + "user", + "folder_name", + "authorize_google_drive_access", + "authorization_code", + "refresh_token" + ], + "fields": [ + { + "default": "0", + "fieldname": "enable", + "fieldtype": "Check", + "label": "Enable" + }, + { + "depends_on": "enable", + "fieldname": "sb_00", + "fieldtype": "Section Break", + "label": "Google Drive" + }, + { + "fieldname": "user", + "fieldtype": "Link", + "in_list_view": 1, + "label": "User", + "options": "User", + "reqd": 1 + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "authorize_google_drive_access", + "fieldtype": "Button", + "label": "Authorize Google Drive Access" + }, + { + "fieldname": "folder_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Folder Name", + "reqd": 1, + "set_only_once": 1, + "unique": 1 + }, + { + "fieldname": "authorization_code", + "fieldtype": "Password", + "label": "Authorization Code" + }, + { + "fieldname": "refresh_token", + "fieldtype": "Password", + "label": "Refresh Token" + } + ], + "modified": "2019-08-09 18:20:58.169056", + "modified_by": "Administrator", + "module": "Integrations", + "name": "Google Drive", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "ASC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/integrations/doctype/google_drive/google_drive.py b/frappe/integrations/doctype/google_drive/google_drive.py new file mode 100644 index 0000000000..92e96e8522 --- /dev/null +++ b/frappe/integrations/doctype/google_drive/google_drive.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import requests +import googleapiclient.discovery +import google.oauth2.credentials + +from frappe import _ +from frappe.model.document import Document +from frappe.utils import get_request_site_address +from six.moves.urllib.parse import quote +from apiclient.http import MediaFileUpload + +SCOPES = "https://www.googleapis.com/auth/drive" + +class GoogleDrive(Document): + + def get_access_token(self): + google_settings = frappe.get_doc("Google Settings") + + if not google_settings.enable: + frappe.throw(_("Google Integration is disabled.")) + + if not self.refresh_token: + button_label = frappe.bold(_("Allow Google Drive 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 Drive 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 authorize_access(g_drive, 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_drive = frappe.get_doc("Google Drive", g_drive) + + redirect_uri = get_request_site_address(True) + "?cmd=frappe.integrations.doctype.google_drive.google_drive.google_callback" + + if not google_drive.authorization_code or reauthorize: + frappe.cache().hset("google_drive", "google_drive", google_drive.name) + return get_authentication_url(client_id=google_settings.client_id, redirect_uri=redirect_uri) + else: + try: + data = { + "code": google_drive.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 Drive", google_drive.name, "refresh_token", r.get("refresh_token")) + frappe.db.commit() + + frappe.local.response["type"] = "redirect" + frappe.local.response["location"] = "/desk#Form/{0}/{1}".format(quote("Google Drive"), quote(google_drive.name)) + + frappe.msgprint(_("Google Drive has been configured.")) + except Exception as e: + frappe.throw(e) + +def get_authentication_url(client_id, redirect_uri): + 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(code=None): + """ + Authorization code is sent to callback as per the API configuration + """ + google_drive = frappe.cache().hget("google_drive", "google_drive") + frappe.db.set_value("Google Drive", google_drive, "authorization_code", code) + frappe.db.commit() + + authorize_access(google_drive) + +def get_google_drive_object(g_drive): + """ + Returns an object of Google Drive. + """ + google_settings = frappe.get_doc("Google Settings") + account = frappe.get_doc("Google Drive", g_drive) + + credentials_dict = { + "token": account.get_access_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), + "scopes": "https://www.googleapis.com/auth/drive/v3" + } + + credentials = google.oauth2.credentials.Credentials(**credentials_dict) + google_drive = googleapiclient.discovery.build("drive", "v3", credentials=credentials) + + return google_calendar + +@frappe.whitelist() +def upload_document(doctype, docname, g_drive): + from frappe.utils.print_format import download_pdf + + google_drive = get_google_drive_object(g_drive) + download_pdf(doctype=doctype, docname=docname, format="pdf") + file_metadata = {"name": frappe.local.response.filename} + media = MediaFileUpload(frappe.local.response.filename, mimetype="application/pdf") + + file = google_drive.files().create(body=file_metadata, media_body=media, fields='id').execute() + + + # frappe.local.response.filecontent diff --git a/frappe/integrations/doctype/google_drive/test_google_drive.py b/frappe/integrations/doctype/google_drive/test_google_drive.py new file mode 100644 index 0000000000..f06e13572c --- /dev/null +++ b/frappe/integrations/doctype/google_drive/test_google_drive.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 TestGoogleDrive(unittest.TestCase): + pass diff --git a/frappe/public/build.json b/frappe/public/build.json index cb41ae7eef..7bec259933 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -205,6 +205,7 @@ "public/js/frappe/ui/toolbar/toolbar.js", "public/js/frappe/ui/toolbar/notifications.js", "public/js/frappe/views/communication.js", + "public/js/frappe/views/google_drive_uploader.js", "public/js/frappe/views/translation_manager.js", "public/js/frappe/ui/sort_selector.html", diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js index 43411bd6de..003f4661e2 100644 --- a/frappe/public/js/frappe/form/toolbar.js +++ b/frappe/public/js/frappe/form/toolbar.js @@ -125,6 +125,15 @@ frappe.ui.form.Toolbar = Class.extend({ } } + //Google Drive + if(frappe.boot.google_drive_enabled){ + this.page.add_menu_item(__("Google Drive Upload"), function() { + new frappe.views.GoogleDriveUploader(me.frm);}, true); + this.print_icon = this.page.add_action_icon("fa fa-google", function() { + new frappe.views.GoogleDriveUploader(me.frm); + }); + } + // email if(frappe.model.can_email(null, me.frm) && me.frm.doc.docstatus < 2) { this.page.add_menu_item(__("Email"), function() { diff --git a/frappe/public/js/frappe/views/google_drive_uploader.js b/frappe/public/js/frappe/views/google_drive_uploader.js new file mode 100644 index 0000000000..7c943216b7 --- /dev/null +++ b/frappe/public/js/frappe/views/google_drive_uploader.js @@ -0,0 +1,52 @@ +frappe.views.GoogleDriveUploader = Class.extend({ + init: function(opts) { + $.extend(this, opts); + this.make(); + }, + make: function() { + let me = this; + let uploader = new frappe.ui.Dialog({ + title: __("Upload File to Google Drive"), + fields: [ + { + fieldtype: "Link", + fieldname: "google_drive", + options: "Google Drive", + label: __("Google Drive"), + reqd: 1, + get_query: function() { + return { + "filters": { + "owner": frappe.session.user, + } + } + } + } + ], + primary_action_label: __("Submit"), + primary_action: (d) => { + frappe.show_alert({ + indicator: "red", + message: __("Uploading to Google Drive.") + }); + uploader.hide(); + frappe.call({ + method: "frappe.integrations.doctype.google_drive.google_drive.upload_document", + args: { + doctype: me.doctype, + docname: me.docname, + g_drive: d.google_drive, + }, + callback: function(r) { + frappe.show_alert({ + indicator: "green", + message: __("Document uploaded to Google Drive.") + }); + uploader.hide(); + } + }) + } + }); + uploader.show(); + } +}) \ No newline at end of file