I am trying to add a new row to an existing sheet with date columns. Some of the cells in the new row hyperlink to other sheets, this works fine. But there are other cells that I want to link_in_to from other sheets. I keep getting an "attributes are not allowed for this operation" error.
there was a comment from an old smartsheet community post indicating that cell links don't work in the 1.1 API. But we are well past that, and the 2.0 documentation implies that it should be possible.
Has anyone else seen this or solved it?
row_a.cells.append({
'column_id': status_columns['Exp Start'],
'value': None,
'linkInFromCell': {
'columnID': project_columns['Start'],
'rowID': project_rows[1],
'sheetID': map_of_sheets[this_project]},
})
The value property must be set to an ExplicitNull (so that it is serialized as null in the JSON body), like this:
cell = smart.models.Cell()
cell.column_id = col_id
cell.link_in_from_cell = cell_link
cell.value = smart.models.ExplicitNull()
row = smart.models.Row()
row.id = added_row.id
row.cells.append(cell)
action = smart.Sheets.update_rows(sheet.id, [row])
Check out test_regression.py in the tests/integration folder, test case test_link_in_from_cell shows the technique.
Related
I am writing a Python script using gspread to read and write data to a Google Sheet. One of the tasks that this script has to do is set the background color of any empty cell in a given column to black. I am attempting to read the sheet data just once into a dictionary called data using data = sheet.get_all_records(), then formatting the cell based on the dictionary's data, however I am running into a problem. worksheet.format() seems to only accept cells in 'A1' notation (that's all that it shows in the docs about formatting cells, and any attempts at other formats throws errors), but the cell object that I have by calling sheet.find(userDict[column]), where userDict[] is an element of data, returns its row and column as an integer. Is there a nice way to have a cell object return its position in 'A1' notation? If not, I suspect the easiest way would be to convert cell.col to the appropriate character using ASCII values, but I'm hoping there's a more elegant way.
Thanks in advance!
I believe your goal is as follows.
For the specific column, you want to set the background color of the cells which are empty.
You want to achieve this using gspread for python.
In this case, how about the following flow?
Retrieve all values from the sheet.
Retrieve the row numbers of the cells which are empty.
Set the background color of the cells using batchUpdate method.
When this flow is reflected in the script, it becomes as follows.
Sample script:
client = gspread.authorize(credentials) # Please use your authorization script.
spreadsheetId = "###" # Please set your Spreadsheet ID.
sheetName = "Sheet1" # Please set the sheet name.
checkColumns = [1, 3] # Please set the column number. In this sample, 1 and 3 means columns "A" and "C", respectively.
# 1. Retrieve all values from the sheet.
spreadsheet = client.open_by_key(spreadsheetId)
sheet = spreadsheet.worksheet(sheetName)
data = sheet.get_all_values()
# 2. Retrieve the row numbers of the cells which are the empty.
transposed = [list(e) for e in zip(*data)]
rowNumbers = [[i for i, v in enumerate(transposed[e - 1]) if not v] for e in checkColumns]
# 3. Set background color of the cells using batchUpdate method.
requests = []
for i, e in enumerate(checkColumns):
if rowNumbers[i] != []:
for f in rowNumbers[i]:
requests.append({
"updateCells": {
"range": {"sheetId": sheet.id, "startRowIndex": f, "endRowIndex": f + 1, "startColumnIndex": e - 1, "endColumnIndex": e},
"rows": [{"values": [{"userEnteredFormat": {"backgroundColor": {"red": 0, "green": 0, "blue": 0}}}]}],
"fields": "userEnteredFormat.backgroundColor"
}
})
res = spreadsheet.batch_update({"requests": requests})
When this script is run, the background color of the empty cells of the specific columns is set as the black color.
References:
batch_update(body)
UpdateCellsRequest
After starting the program
results = smart.Search.search("2244113312180")
print(results)
Getting the data
{"results":
[{"contextData": ["2244113312180"],
"objectId": 778251154810756,
"objectType": "row",
"parentObjectId": 3648397300262788,
"parentObjectName": "Sample Sheet",
"parentObjectType": "sheet",
"text": "2244113312180"},
{"contextData": ["2244113312180"],
"objectId": 7803446734415748,
"objectType": "row",
"parentObjectId": 3648397300262788,
"parentObjectName": "Sample Sheet",
"parentObjectType": "sheet",
"text": "2244113312180"}],
"totalCount": 2}
How do I use them correctly in my program?
Please provide a correct usage example.
And how to find out the id_column in which the value was found "2244113312180"?
new_row = smartsheet.models.Row()
new_row.id = results.objectId
Sorry I didn't write the error right away. I can't use the properties from the results. String:
new_row.id = results.objectId
Causes an error
AttributeError: 'SearchResult' object has no attribute 'objectId'
Thank you for any help!
P.S. I found how to do it.
results = smart.Search.search("2244113312180")
text = str(results)
json_op = json.loads(text)
for i in json_op["results"]:
new_row = smartsheet.models.Row()
new_row.id = i["objectId"]
I don't know if this is a good solution or not.
According to the SearchResultItem Object definition in the Smartsheet API docs, a search result item will never contain information about the column where a value exists. As the result JSON you've posted shows, if the specified value is found within the row of a sheet (i.e., in any of the cells that row contains), the corresponding search result item will identify the sheet ID (parentObjectId) and the row ID (objectId).
You can then use those two values to retrieve the row, as described in the Get Row section of the docs:
row = smartsheet_client.Sheets.get_row(
4583173393803140, # sheet_id
2361756178769796 # row_id
)
Then you can iterate through the row.cells array, checking the value property of each cell to determine if it matches the value you searched for previously. When you find a cell object that contains that value, the column_id property of that cell object will give you the column ID where the matching value exists.
UPDATE:
Thanks for clarifying info in your original post. I'm updating this answer to provide a complete code sample that implements the approach I described previously. Hope this is helpful!
This code sample does the following:
searches everything in Smartsheet (that the holder of the API token being used has access to) for a string value
iterates through search result items to process any "row" results (i.e., anywhere that the string appears within the cells of a sheet)
replaces any occurrences within (the cells of) a sheet with the string new value
# set search criteria
query = '2244113312180'
# search everything
search_results = smart.Search.search(query)
# loop through results
# (acting upon only search results that appear within a row of a sheet)
for item in search_results.results:
if item.object_type == 'row':
# get row
row = smart.Sheets.get_row(
item.parent_object_id, # sheet_id
item.object_id # row_id
)
# find the cell that contains the value and update that cell value
for cell in row.cells:
if cell.value == query:
# build new cell value
new_cell = smartsheet.models.Cell()
new_cell.column_id = cell.column_id
new_cell.value = "new value"
new_cell.strict = False
# build the row to update
new_row = smartsheet.models.Row()
new_row.id = item.object_id
new_row.cells.append(new_cell)
# update row
result = smart.Sheets.update_rows(
item.parent_object_id, # sheet_id
[new_row])
https://developers.google.com/sheets/api/reference/rest
I was able to Create sheet and also clear sheet and some other operations
But I am not able to get "number of row and the column" that are already filled, such that I can add some more data in the sheet.
def nextAvailableRow(worksheet):
rowsUsed = len(filter(None, worksheet.col_values(1)))
return rowsUsed+1
This function should take in a worksheet as a parameter and output the next available row that is not used. It takes in all the values of the first column and then filters out all the empty rows to find how many rows have been used.
If you only want to insert new data after the last row with values, you can do it using the spreadsheets.values.append endpoint (Notice you can play with the API at "try this API" and there is also a Python example)
spreadsheet_id = 'your-spreadSheet-id'
ranges = "A1:A" # It will get the indo until the last row with data
value_render_option = "DIMENSION_UNSPECIFIED"
value_input_option = "USER_ENTERED"
# Body for the request
value_range_body = {
"values": [
[
"A11",
"B11"
],
[
"A12",
"B12"
]
],
"majorDimension": "DIMENSION_UNSPECIFIED"
}
request = service.spreadsheets().values()\
.append(spreadsheetId=spreadsheet_id, range=ranges, valueInputOption=value_input_option, body=value_range_body)
request.execute()
The outer array in values will represent the rows and the inner ones the columns as you can see in the example body in my code.
Notice: In another answer, someone is recommending you to use a third library, instead of Google Sheet's client library for Python. I would recommend you to use the Official Google library because it has the guarantee to be supported by Google.
I am using Python. I need to add sheet in spreadsheets using Google API v4. I can create a sheet using batchUpdate with spreadsheet id and addSheet request (it returns shetId and creates empty sheet). But how can I add data in it?
data = {'requests': [
{
'addSheet':{
'properties':{'title': 'New sheet'}
}
}
]}
res = service.spreadsheets().batchUpdate(spreadsheetId=s_id, body=data).execute()
SHEET_ID = res['replies'][0]['addSheet']['properties']['sheetId']
You can add this code to write data in Google Sheet. In the document - Reading & Writing Cell Values
Spreadsheets can have multiple sheets, with each sheet having any number of rows or columns. A cell is a location at the intersection of a particular row and column, and may contain a data value. The Google Sheets API provides the spreadsheets.values collection to enable the simple reading and writing of values.
Writing to a single range
To write data to a single range, use a spreadsheets.values.update request:
values = [
[
# Cell values ...
],
# Additional rows ...
]
body = {
'values': values
}
result = service.spreadsheets().values().update(
spreadsheetId=spreadsheet_id, range=range_name,
valueInputOption=value_input_option, body=body).execute()
The body of the update request must be a ValueRange object, though the only required field is values. If range is specified, it must match the range in the URL. In the ValueRange, you can optionally specify its majorDimension. By default, ROWS is used. If COLUMNS is specified, each inner array is written to a column instead of a row.
Writing multiple ranges
If you want to write multiple discontinuous ranges, you can use a spreadsheets.values.batchUpdate request:
values = [
[
# Cell values
],
# Additional rows
]
data = [
{
'range': range_name,
'values': values
},
# Additional ranges to update ...
]
body = {
'valueInputOption': value_input_option,
'data': data
}
result = service.spreadsheets().values().batchUpdate(
spreadsheetId=spreadsheet_id, body=body).execute()
The body of the batchUpdate request must be a BatchUpdateValuesRequest object, which contains a ValueInputOption and a list of ValueRange objects (one for each written range). Each ValueRange object specifies its own range, majorDimension, and the data to input.
Hope this helps.
Figured out that after creating sheet inside spreadsheet you can access range 'nameofsheet!A1' i.e.
service.spreadsheets().values().update(spreadsheetId=s_id, range='New sheet!A1', body=data_i, valueInputOption='RAW').execute()
This request will post the data into new created sheet with name 'New sheet'
You could also try a 'more intuitive' library i wrote for google sheets api v4 , pygsheets as i found the default client bit cumbersome.
import pygsheets
gc = pygsheets.authorize()
# Open spreadsheet
sh = gc.open('my new ssheet')
# create a worksheet
sh.add_worksheet("new sheet",rows=50,cols=60)
# update data with 2d vector
sh.update_cells('A1:B10', values)
# or skip let it infer range from size of values
sh.update_cells('A1', values)
I need to add multiple (few hundreds) rows into google spreadsheet. Currently I'm doing it in a loop:
for row in rows
_api_client.InsertRow(row, _spreadsheet_key, _worksheet_id)
which is extremely slow, because rows are added one by one.
Is there any way to speed this up?
Ok, I finally used batch request. The idea is to send multiple changes in a one API request.
Firstly, I created a list of dictionaries, which will be used like rows_map[R][C] to get value of cell at row R and column C.
rows_map = [
{
1: row['first_column']
2: row['second']
3: row['and_last']
}
for row i rows
]
Then I get all the cells from the worksheet
query = gdata.spreadsheet.service.CellQuery()
query.return_empty = 'true'
cells = _api_client.GetCellsFeed(self._key, wksht_id=self._raw_events_worksheet_id, query=query)
And create batch request to modify multiple cells at a time.
batch_request = gdata.spreadsheet.SpreadsheetsCellsFeed()
Then I can modify (or in my case rewrite all the values) the spreadsheet.
for cell_entry in cells.entry:
row = int(cell_entry.cell.row) - 2
col = int(cell_entry.cell.col)
if 0 <= row < len(events_map):
cell_entry.cell.inputValue = rows_map[row][col]
else:
cell_entry.cell.inputValue = ''
batch_request.AddUpdate(cell_entry)
And send all the changes in only one request:
_api_client.ExecuteBatch(batch_request, cells.GetBatchLink().href)
NOTES:
Batch request are possible only with Cell Queries. There is no such mechanism to be used with List Queries.
query.return_empty = 'true' is mandatory. Otherwise API will return only cells which are not empty.