Last active
June 1, 2025 09:46
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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