Use gspread and Google sheets API to copy a sheet to multiple workbooks - google-sheets-api

I have a google sheet file called "template". It has 2 worksheets. I have 100 other files called "Instance01", "Instance02", etc. which have 2 worksheets with the same name as the ones in "template".
I have created a new worksheet in "template", called "sheet3". I want to copy it to each of the 100 "Instance" documents, keeping the name "sheet3" in each Instance.
Can I achieve that with gspread? And how? If not, is there another way to do it programatically (or within reasonable time and effort)?

#Tanaike comment showed the way. I document the code I used below.
# Build the service and gspread agent
sheets_service = build('sheets', 'v4', credentials=credentials)
gc = gspread.authorize(credentials)
# Create functions to rename the sheet after copying
# For renaming sheets
# https://stackoverflow.com/questions/38074069/how-do-i-rename-a-worksheet-in-a-google-sheets-spreadsheet-using-the-api-in-py
def batch(requests, spreadsheetId, service):
body = {
'requests': requests
}
return service.spreadsheets().batchUpdate(spreadsheetId=spreadsheetId, body=body).execute()
def renameSheet(spreadsheetId, sheetId, newName, service):
return batch({
"updateSheetProperties": {
"properties": {
"sheetId": sheetId,
"title": newName,
},
"fields": "title",
}
},
spreadsheetId,
service)
# Now execute the copies
# The ID of the spreadsheet containing the sheet to copy. Everybody has access!!!
spreadsheet_id = ORIGINAL_spreadsheet_id # template workbook
# The ID of the sheet to copy. Everybody has access!!!
sheet_id = original_sheet_id # template sheet id
for workbook_id in COPY_TO: #COPY_TO is a list of workbooks ids
print(workbook_id)
destiny_spreadsheet_id = workbook_id
copy_sheet_to_another_spreadsheet_request_body = {
"destinationSpreadsheetId": destiny_spreadsheet_id
}
request = sheets_service.spreadsheets().sheets().copyTo(spreadsheetId=spreadsheet_id, sheetId=sheet_id, body=copy_sheet_to_another_spreadsheet_request_body)
response = request.execute()
# Rename it
copied_to_sheet = gc.open_by_key(destiny_spreadsheet_id) #template.worksheet(destiny)
sheet = copied_to_sheet.worksheet('Copy of ' + NAME_OF_SHEET_TO_BE_COPIED)
renameSheet(destiny_spreadsheet_id, sheet.id, NAME_OF_SHEET_TO_BE_COPIED, sheets_service)

As the method for copying the sheets, there is the method of "spreadsheets.sheets.copyTo" in Sheets API.
You can see a sample script at this thread. I thought that because the object of credentials can be used for both gspread and googleapiclient, it might help to implement this.
Reference:
spreadsheets.sheets.copyTo

Related

pdf is created from google slide, but with the markers populated (via GAS)

The code below basically maps columns from a spreadsheet to a couple of markers I got on a google slide.
It generates copies of the google slide template, updates them with the row's data and I actually need it to be in pdf form to be emailed later.
The pdf files are created in the destination folder, with the right file names, but the markers within them are "empty". Later on, I will have to delete these google slide files, but the challenge here now is to have the pdf files correctly created.
Appreciate your time.
function mailMerge(templateID,ssID, sheetName, mapped, fileNameData, emailCol, rowLen = "auto"){
//Properties Services is Google Script Storage.
//This clears out the storage.
PropertiesService.getScriptProperties().deleteAllProperties();
const ss = SpreadsheetApp.getActiveSpreadsheet();
//const sheet = SpreadsheetApp.openById(ssID);
const sheet = ss.getSheetByName("Lista de Participantes");
//Get number of rows to process
rowLen = (rowLen = "auto") ? getRowLen() : rowLen;
const range = sheet.getRange(7,1,rowLen,sheet.getDataRange().getNumColumns());
const matrix = range.getValues();
const fileNameRows = getFileNameRows()
for(let i = 1; i < rowLen; i++){
if (matrix[i][1] == true && matrix[i][27] != "Sim") {
let row = matrix[i];
//Get the title for the file.
let fileName = buildFileName(row)
//Creates a copy of the template file and names it with the current row's details.
let newDoc = DriveApp.getFileById(templateID).makeCopy(fileName);
//Replaces all the text place markers ({{text}}) with current row information.
updateFileData(row, newDoc.getId());
//Save new File ID and email to Properties service.
PropertiesService.getScriptProperties()
.setProperty(newDoc.getId(),row[emailCol]);
// 5. Export the temporal Google Slides as a PDF file.
newDoc = DriveApp.getFileById(newDoc.getId());
DriveApp.getFolderById("folder ID").createFile(newDoc.getBlob());
}
};
Besides the code above, I go this script file within the same container/Spreadsheet, where I map the columns whose data I want to generate a google Slide for. each column of data I refer to as marker.
/*###################################################################
* Maps the relationship between the Google Sheet header and its location
* for each column along with it's corresponding Google Slide Doc template name.
*
* To update change the sheet, col and doc:
* ***
* {
* sheet: << Your sheet header
* col: << The column on the google sheet with the above header
* doc: << the corresonding name in double braces {{name}} in your Slide template
* }
* ***
*###################################################################
*/
const mappedDocToSheet = [
{
sheet:"Nome",
col:2,
doc:"primeiroNome"
},
{
sheet:"Sobrenome",
col:3,
doc:"sobrenome"
},
{
sheet:"COD. CERTIFICADO",
col:9,
doc:"codigo"
},
{
sheet:"Curso",
col:10,
doc:"curso"
},
];
I believe your goal and situation as follows.
You add the values of Google Slides and create it to PDF data
newDoc is the Google Slides
In order to achieve your goal, please use saveAndClose. For your script, please modify as follows.
Modified script:
Please add the following script to your function of mailMerge as follows.
// 5. Export the temporal Google Slides as a PDF file.
SlidesApp.openById(newDoc.getId()).saveAndClose(); // <--- Added
Reference:
saveAndClose()

How to update a different google sheet when pulling information from the first sheet?

I'm able to pull from googlesheets and update the same sheet I pulled from. (The script will only work for the first sheet for the google spreadsheet.)
After pulling from the first sheet I want to generate a report which works, but I don't want it to overwrite the first sheet, instead I want it to put the information on a separate sheet I made. Here's what I have sofar when trying to change my script to work with a different sheet.
def get_google_sheet(spreadsheet_id, range_name):
# Retrieve sheet data using OAuth credentials and Google Python API.
global scopes
global gsheet
scopes = ['https://www.googleapis.com/auth/spreadsheets']
# Setup the Sheets API
store = file.Storage('credentials1.json')
creds = store.get()
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets('client_secret.json', scopes)
creds = tools.run_flow(flow, store)
service = build('sheets', 'v4', http=creds.authorize(Http()))
# Call the Sheets API
gsheet = service.spreadsheets().values().get(spreadsheetId=spreadsheet_id, range=range_name).execute()
return gsheet
def gsheet2df(gsheet):
# Converts Google sheet data to a Pandas DataFrame.
header = gsheet.get('values', [])[0] # Assumes first line is header!
values = gsheet.get('values', [])[1:] # Everything else is data.
#header = values[0]
if not values:
print('No data found.')
else:
df = pd.DataFrame.from_records(values)
df.columns = header
return df
def Export_Data_To_Sheets():
store = file.Storage('credentials.json')
creds = store.get()
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets('client_secret.json', scopes)
creds = tools.run_flow(flow, store)
service = build('sheets', 'v4', http=creds.authorize(Http()))
RANGE_AND_SHEET = "ReportListA1:E500"
response_date = service.spreadsheets().values().update(
spreadsheetId=SPREADSHEET_ID,
valueInputOption='RAW',
range=RANGE_AND_SHEET,
body=dict(
majorDimension='ROWS',
values=df.T.reset_index().T.values.tolist())
).execute()
print('Sheet successfully Updated')
When I use just the range without identifying the sheet it works no problem. With the range and sheet I get this error.
Traceback (most recent call last):
File "/Users/adaniel/PycharmProjects/EmailAutomation/venv/AutoEmailScript/test.py", line 126, in <module>
Export_Data_To_Sheets()
File "/Users/adaniel/PycharmProjects/EmailAutomation/venv/AutoEmailScript/test.py", line 68, in Export_Data_To_Sheets
response_date = service.spreadsheets().values().update(
File "/Users/adaniels/PycharmProjects/EmailAutomation/venv/lib/python3.8/site-packages/googleapiclient/_helpers.py", line 130, in positional_wrapper
return wrapped(*args, **kwargs)
File "/Users/adaniels/PycharmProjects/EmailAutomation/venv/lib/python3.8/site-packages/googleapiclient/http.py", line 840, in execute
raise HttpError(resp, content, uri=self.uri)
googleapiclient.errors.HttpError: <HttpError 400 when requesting https://sheets.googleapis.com/v4/spreadsheets/*******yAABhNJvARr9BzsVLvCYSfpcvQAmpWESL-DJI/values/%28%27ReportList%21%27%2C%20%27A1%3AE500%27%29?valueInputOption=RAW&alt=json returned "Invalid JSON payload received. Unexpected token.
House", "10/19/19", NaN, NaN, NaN], ["Co
^">
I already have two different credential files for different authentication scopes. I also deleted them and reauthenticated, so I'm not sure why I'm getting a 400 error.
Answer:
You need to specify the sheet you wish to update in the range portion of the request.
More Information:
service.open('SheetName').sheet is not a thing - open is not a method of the service, the Sheets service doesn't know at this point what Spreadsheet you are trying to update the values of.
As per the documentation, you need to specify the range of values you wish to update in the range parameter. This includes the Sheet name.
Code Snippet:
RANGE_AND_SHEET = "ReportList!" + RANGE_NAME
response_date = service.spreadsheets().values().update(
spreadsheetId=SPREADSHEET_ID,
range=RANGE_AND_SHEET,
valueInputOption='RAW',
body=value_range_body).execute()
References:
Method: spreadsheets.values.update | Sheets API | Google Developers

Data validation with One of range rule in Google Sheets API

I'm trying to implement data validation where the rule is one of the range using Google Sheets API.
In sheet1, I have a master list where one column needs to be in one of the values. The possible dropdown values are in a separate sheet called dropdown.
What is the error in my conditional value for one_of_range?
dropdown_action = {
'setDataValidation':{
'range':{
'startRowIndex':1,
'startColumnIndex':4,
'endColumnIndex':5
},
'rule':{
'condition':{
'type':'ONE_OF_RANGE',
'values': [
{ "userEnteredValue" : "dropdown!A1:B2"
}
],
},
'inputMessage' : 'Choose one from dropdown',
'strict':True,
'showCustomUi': True
}
}
}
request = [dropdown_action]
batchUpdateRequest = {'requests': request}
SHEETS.spreadsheets().batchUpdate(spreadsheetId = id,
body = batchUpdateRequest).execute()
However, I encountered into http error. I was able to get it working if I choose one of list instead of one_of_range. But I prefer to use one_of_range so that I can maintain the possible values in the same spreadsheet.
HttpError 400 when requesting https://sheets.googleapis.com/v4/spreadsheets/id:batchUpdate?alt=json returned "Invalid requests[1].setDataValidation: Invalid ConditionValue.userEnteredValue: dropdown!A1:B2">
As Sam Berlin suggested, the solution is to add '=' in the range.
"=dropdown!A1:B2" will work with one_in_range data validation rule.
Nothing could work for me, and then I saw this subject
Google Spreadsheet API setDataValidation with Regex , where ZektorH mark that "Your expression needs to be escaped".

Copying Text File Data into existing Excel Workbook Using PowerShell

So I'm having a problem exporting data from a Text.File into a Excel Workbook that contains data from a month. What ends up happening is, the code opens a new workbook with the name and tittle of the sheet called 'Geraldine3-16-2016', I don't mind that, however it never gets added or copied to the the main workbook. So eventually, the only thing that changes in the main workbook is that a new sheet gets created called 'Sheet 1'but there is no data from the text file. Any help is greatly appreciated, Thank you in advance!
function Release-Ref ($ref) {
([System.Runtime.InteropServices.Marshal]::ReleaseComObject(
[System.__ComObject]$ref) -gt 0)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
$File='C:\users\cesar.sanchez\downloads\Returns data 2-16-15.xlsx'
$TextFile='C:\Users\cesar.sanchez\downloads\Geraldine3-16-2016.txt'
$Excel = New-Object -C Excel.Application
$Excel.Visible=$true #For troubleshooting purposes only.
# $Excel.DisplayAlerts = $false
$TextData = $Excel.Workbooks.Opentext($TextFile,$null,$true)
$ExcelData = $Excel.Workbooks.Open($File) # Open Template
$NewS_ExcelData=$ExcelData.sheets.add()
$TexttoCopy=$TextData.Sheets.item(1)
$TexttoCopy.copy($NewS_ExcelData)
I believe it has to do with something in this part of the code but I'm not completely sure.
$NewS_ExcelData=$ExcelData.sheets.add()
$TexttoCopy=$TextData.Sheets.item(1)
$TexttoCopy.copy($NewS_ExcelData)
.OpenText is not the same as .Open. It does not return an object. (found out the hard way!)
$TexttoCopy=$TextData.Sheets.item(1) throws the error:
You cannot call a method on a null-valued expression.
Alternative code:
$File='C:\users\cesar.sanchez\downloads\Returns data 2-16-15.xlsx'
$TextFile='C:\Users\cesar.sanchez\downloads\Geraldine3-16-2016.txt'
$Excel = New-Object -C Excel.Application
$Excel.Visible=$true #For troubleshooting purposes only.
# $Excel.DisplayAlerts = $false
$Excel.Workbooks.Opentext($TextFile,$null,$true) # Open Text file
$TextData = $Excel.ActiveWorkbook # Assign active workbook
$ExcelData = $Excel.Workbooks.Open($File) # Open Template
$NewS_ExcelData = $ExcelData.sheets.add()
$TexttoCopy = $TextData.Sheets.item(1)
$TexttoCopy.copy($NewS_ExcelData)
You may also find $TexttoCopy.move($NewS_ExcelData) useful.

Setting Sharepoint File Field Attributes

In out SP site, we have a library with files. These are files associated with a user. We now cstomized the user's profiles to accept a list of files. And now, to this list of files in the user's profile, we would like to add a reference to the file so that the user doesn't have to upload again.
Current Library:
/personal/my/User Files/[filename]
So, I was wondering how to do this? The data looks like this in the new User Files field (JSON):
{
[
{
"Id":"1",
"Title":"Test",
"Url":"\/personal\/my\/User+Files\/testfile.doc"
}
]
}
I have a csv file that I iterate over. The csv file contains the user name:filename pairs.
The Id value has to be gotten from the SP instance libarary at that location for that file.
Powershell code:
$upAttribute = "UserFiles"
$profile_info = Import-Csv profiles.csv
foreach ($row in $profile_info) {
$userId = $row.user # User ID
$fullAdAccount = $adAccount += $userId
#Check to see if user profile exists
if ($profileManager.UserExists($fullAdAccount))
{
$up = $profileManager.GetUserProfile($fullAdAccount)
$upAttributeValue += $row.filename # Filename
# CODE ??????
$up.Commit()
}
}
That is the all the data that I have.
Thanks for any and all help.
Eric
You will first need to add the custom property to the User Profile like so:
http://www.paulgrimley.com/2011/02/adding-custom-user-profile-property-to.html
Then this should help you out:
http://get-spscripts.com/2010/07/modify-single-value-user-profile.html
#Get user profile and change the value
$up = $profileManager.GetUserProfile($adAccount)
$up[$upAttribute].Value = $upAttributeValue
$up.Commit()