Skip to content

Instantly share code, notes, and snippets.

@vr-greycube
Last active June 1, 2025 09:46
Show Gist options
  • Save vr-greycube/8faf1be50deb52713792de028c590476 to your computer and use it in GitHub Desktop.
Save vr-greycube/8faf1be50deb52713792de028c590476 to your computer and use it in GitHub Desktop.
Create Google Spreadsheet in Frappe, using frappe built-in oauth setup in Google Settings for Google Drive
import frappe
from frappe.utils import cstr
from frappe.integrations.google_oauth import GoogleOAuth
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
from frappe.desk.query_report import run
class GoogleOAuthSpreadsheets(GoogleOAuth):
"""
Override GoogleOAuth as it does not include sheets service
The google project needs to have Drive and Sheets service enabled
"""
def __init__(self, validate: bool = True):
self.google_settings = frappe.get_single("Google Settings")
self.domain = ('sheets', 'v4')
self.scopes = "https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive"
def get_google_service_object(self, access_token: str, refresh_token: str):
"""Returns google service object"""
credentials_dict = {
"token": access_token,
"refresh_token": refresh_token,
"token_uri": self.OAUTH_URL,
"client_id": self.google_settings.client_id,
"client_secret": self.google_settings.get_password(
fieldname="client_secret", raise_exception=False
),
"scopes": self.scopes,
}
return build(
serviceName=self.domain[0],
version=self.domain[1],
credentials=Credentials(**credentials_dict),
static_discovery=False,
)
class GSpreadsheet():
def __init__(self):
self.account = frappe.get_doc("Google Drive")
self.oauth_obj = GoogleOAuthSpreadsheets()
# spreadsheets service
self.spreadsheet_service = self.oauth_obj.get_google_service_object(
self.account.get_access_token(),
self.account.get_password(
fieldname="indexing_refresh_token", raise_exception=False),
)
# drive service
self.drive_oauth_obj = GoogleOAuth("drive")
self.drive_service = self.drive_oauth_obj.get_google_service_object(
self.account.get_access_token(),
self.account.get_password(
fieldname="indexing_refresh_token", raise_exception=False),
)
def create_sheet(self, title, data=[], folder_id=None, share_with=""):
"""
Create a new Google Spreadsheet, optionally insert data, move it into a specific folder,
and share it with a user.
Args:
title (str): The title of the new Google Sheet.
data (list, optional): A 2D list representing rows and columns to insert into the sheet.
Example: [['Name', 'Email'], [
'Alice', '[email protected]']]
folder_id (str, optional): The ID of the Google Drive folder to place the new sheet in.
If not provided, the sheet will be created in the root directory.
share_with (str, optional): semi-colon seperated Email address to share the sheet with. If provided, the sheet
will be shared with edit access.
Returns:
dict: A dictionary containing 'spreadsheetId' and 'spreadsheetUrl' of the created sheet.
Raises:
Google API errors via HttpError if creation, data insertion, or sharing fails.
Example:
gsheet = GSpreadsheet()
gsheet.create_sheet(
title="Team Contacts",
data=[["Name", "Phone"], ["John", "12345"]],
folder_id="abc123folderid",
share_with="[email protected]"
)
"""
spreadsheet_body = {
'properties': {
'title': title
}
}
spreadsheet = self.spreadsheet_service.spreadsheets().create(body=spreadsheet_body,
fields='spreadsheetId,spreadsheetUrl').execute()
range_name = "Sheet1!A1"
body = {"values": data}
self.spreadsheet_service.spreadsheets().values().update(
spreadsheetId=spreadsheet["spreadsheetId"],
range=range_name,
valueInputOption="RAW",
body=body,
).execute()
if folder_id:
self.drive_service.files().update(
fileId=spreadsheet["spreadsheetId"],
addParents=folder_id,
removeParents='root',
fields='id, parents'
).execute()
if share_with:
if isinstance(share_with, str):
share_with = share_with.split(";")
def callback(request_id, response, exception):
if exception:
frappe.log_error(
title="Export to Sheets:Drive share error.", message=cstr(exception)
)
batch = self.drive_service.new_batch_http_request(
callback=callback)
for email in share_with:
user_permission = {"type": "user",
"role": "writer", "emailAddress": email}
batch.add(
self.drive_service.permissions().create(
fileId=spreadsheet["spreadsheetId"],
body=user_permission,
fields="id",
)
)
batch.execute()
return spreadsheet
@frappe.whitelist()
def save_to_sheets(report_name, filters, share_with):
title = f"{report_name} Export on {frappe.utils.nowdate()}"
out = {}
try:
response = run(report_name, filters)
header = [d["label"] for d in response["columns"]]
fieldnames = [d["fieldname"] for d in response["columns"]]
records = [header]
data = response["result"]
for d in data:
if isinstance(d, list):
records.append(d)
else:
records.append([d.get(f) for f in fieldnames])
gsheet = GSpreadsheet()
out = gsheet.create_sheet(
title=title,
data=records,
# folder_id="",
share_with=share_with
)
out['title'] = title
except Exception as ex:
frappe.throw("This report could not be saved to sheets. {}".format(ex))
return out
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment