I have an existing worksheet with an existing NamedRange for it and I would like to call the batch_update method of the API to protect that range from being edited by anyone other than the user that makes the batch_update call.
I have seen an example on how to add protected ranges via a new range definition, but not from an existing NamedRange.
I know I need to send the addProtectedRangeResponse request. Can I define the request body with a Sheetname!NamedRange notation?
this_range = worksheet_name + "!" + nrange
batch_update_spreadsheet_request_body = {
'requests': [
{
"addProtectedRange": {
"protectedRange": {
"range": {
"name": this_range,
},
"description": "Protecting xyz",
"warningOnly": False
}
}
}
],
}
EDIT: Given #Tanaike feedback, I adapted the call to something like:
body = {
"requests": [
{
"addProtectedRange": {
"protectedRange": {
"namedRangeId": namedRangeId,
"description": "Protecting via gsheets_manager",
"warningOnly": False,
"requestingUserCanEdit": False
}
}
}
]
}
res2 = service.spreadsheets().batchUpdate(spreadsheetId=ssId, body=body).execute()
print(res2)
But although it lists the new protections, it still lists 5 different users (all of them) as editors. If I try to manually edit the protection added by my gsheets_manager script, it complains that the range is invalid:
Interestingly, it seems to ignore the requestUserCanEdit flag according to the returning message:
{u'spreadsheetId': u'NNNNNNNNNNNNNNNNNNNNNNNNNNNN', u'replies': [{u'addProtectedRange': {u'protectedRange': {u'requestingUserCanEdit': True, u'description': u'Protecting via gsheets_manager', u'namedRangeId': u'1793914032', u'editors': {}, u'protectedRangeId': 2012740267, u'range': {u'endColumnIndex': 1, u'sheetId': 1196959832, u'startColumnIndex': 0}}}}]}
Any ideas?
How about using namedRangeId for your situation? The flow of the sample script is as follows.
Retrieve namedRangeId using spreadsheets().get of Sheets API.
Set a protected range using namedRangeId using spreadsheets().batchUpdate of Sheets API.
Sample script:
nrange = "### name ###"
ssId = "### spreadsheetId ###"
res1 = service.spreadsheets().get(spreadsheetId=ssId, fields="namedRanges").execute()
namedRangeId = ""
for e in res1['namedRanges']:
if e['name'] == nrange:
namedRangeId = e['namedRangeId']
break
body = {
"requests": [
{
"addProtectedRange": {
"protectedRange": {
"namedRangeId": namedRangeId,
"description": "Protecting xyz",
"warningOnly": False
}
}
}
]
}
res2 = service.spreadsheets().batchUpdate(spreadsheetId=ssId, body=body).execute()
print(res2)
Note:
This script supposes that Sheets API can be used for your environment.
This is a simple sample script. So please modify it to your situation.
References:
ProtectedRange
Named and Protected Ranges
If this was not what you want, I'm sorry.
Edit:
In my above answer, I modified your script using your settings. If you want to protect the named range, please modify body as follows.
Modified body
body = {
"requests": [
{
"addProtectedRange": {
"protectedRange": {
"namedRangeId": namedRangeId,
"description": "Protecting xyz",
"warningOnly": False,
"editors": {"users": ["### your email address ###"]}, # Added
}
}
}
]
}
By this, the named range can be modified by only you. I'm using such settings and I confirm that it works in my environment. But if in your situation, this didn't work, I'm sorry.
Related
So I'm new to graphQL and I've been figuring out the Uniswap API, through the sandbox browser, but I'm running this program which just gets metadata on the top 100 tokens and their relative pools, but the pool one isn't working at all. I'm trying to put two conditions of if token0's hash is this and token1's hash is this, it should output the pool of those two, however if only outputs pools with the token0 hash, and just ignores the second one. I've tried using and, _and, or two where's seperated by {} or , so on so forth. This is an example I have (python btw):
class ExchangePools:
def QueryPoolDB(self, hash1, hash2):
query = """
{
pools(where: {token0: "%s"}, where: {token1:"%s"}, first: 1, orderBy:volumeUSD, orderDirection:desc) {
id
token0 {
id
symbol
}
token1 {
id
symbol
}
token1Price
}
}""" % (hash1, hash2)
return query
or in the sandbox explorer this:
{
pools(where: {token0: "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"} and: {token1:"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"}, first: 1, orderBy:volumeUSD, orderDirection:desc) {
id
token0 {
id
symbol
}
token1 {
id
symbol
}
token1Price
}
}
with this output:
{
"data": {
"pools": [
{
"id": "0x4585fe77225b41b697c938b018e2ac67ac5a20c0",
"token0": {
"id": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",
"symbol": "WBTC"
},
"token1": {
"id": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"symbol": "WETH"
},
"token1Price": "14.8094450357546760737720184457113"
}
]
}
}
How can I get the API to register both statements?
I have a set of ad creatives that I retreive through the Facebook Business Python SDK. I need these specifically to retreive the outbound URL when someone clicks on the ad: AdCreative['object_story_spec']['video_data']['call_to_action']['value']['link'].
I use the following call:
adcreatives = set.get_ad_creatives(fields=[
AdCreative.Field.id,
AdCreative.Field.name,
AdCreative.Field.object_story_spec,
AdCreative.Field.effective_object_story_id ,
])
Where set is an ad set.
For some cases, the result looks like this (with actual data removed), which is expected:
<AdCreative> {
"body": "[<BODY>]",
"effective_object_story_id": "[<EFFECTIVE_OBJECT_STORY_ID>]",
"id": "[<ID>]",
"name": "[<NAME>]",
"object_story_spec": {
"instagram_actor_id": "[<INSTAGRAM_ACTOR_ID>]",
"page_id": "[<PAGE_ID>]",
"video_data": {
"call_to_action": {
"type": "[<TYPE>]",
"value": {
"link": "[<LINK>]", <== This is what I need
"link_format": "[<LINK_FORMAT>]"
}
},
"image_hash": "[<IMAGE_HASH>]",
"image_url": "[<IMAGE_URL>]",
"message": "[<MESSAGE>]",
"video_id": "[<VIDEO_ID>]"
}
}
}
While sometimes results look like this:
<AdCreative> {
"effective_object_story_id": "[<EFFECTIVE_OBJECT_STORY_ID>]",
"id": "[<ID>]",
"name": "[<NAME>]",
"object_story_spec": {
"instagram_actor_id": "[<INSTAGRAM_ACTOR_ID>]",
"page_id": "[<PAGE_ID>]"
}
}
According to this earlier question: Can't get AdCreative ObjectStorySpec this is due to the fact that the object_story_spec is not populated if it is linked to a creative, instead of created along with the creative.
However, the video_data (and as such, the link), should be saved somewhere. Is there a way to retreive this? Maybe through effective_object_story_id?
The documentation page for object_story_spec (https://developers.facebook.com/docs/marketing-api/reference/ad-creative-object-story-spec/v12.0) does not have the information I am looking for.
I'm trying to use the mediaItems().search() method, using the following body:
body = {
"pageToken": page_token if page_token != "" else "",
"pageSize": 100,
"filters": {
"contentFilter": {
"includedContentCategories": {"LANDSCAPES","CITYSCAPES"}
}
},
"includeArchiveMedia": include_archive
}
but the problem is that the set {"LANDSCAPES","CITYSCAPES"} should actually be a set of enums (as in Java enums), and not strings as ive written. this is specified in the API: (https://developers.google.com/photos/library/reference/rest/v1/albums)
ContentFilter - This filter allows you to return media items based on the content type.
JSON representation
{
"includedContentCategories": [
enum (ContentCategory)
],
"excludedContentCategories": [
enum (ContentCategory)
]
}
is there a proper way of solving this in python?
Modification points:
When albumId and filters are used, an error of The album ID cannot be set if filters are used. occurs. So when you want to use filters, please remove albumId.
The value of includedContentCategories is an array as follows.
"includedContentCategories": ["LANDSCAPES","CITYSCAPES"]
includeArchiveMedia is includeArchivedMedia.
Please include includeArchivedMedia in filters.
When above points are reflected to your script, it becomes as follows.
Modified script:
body = {
# "albumId": album_id, # <--- removed
"pageToken": page_token if page_token != "" else "",
"pageSize": 100,
"filters": {
"contentFilter": {
"includedContentCategories": ["LANDSCAPES", "CITYSCAPES"]
},
"includeArchivedMedia": include_archive
}
}
Reference:
Method: mediaItems.search
I'm using python 2.7 but my understanding is this syntax works with any language.
I'd like to append data to the bottom of my Google Sheets. The function below works, but I'd like to know if there is a better way to:
specify userEnteredValue on the column level instead of cell level
not have to repeat the values list for each column.
An example with my current method:
def insertOneRecord(sheetId, value):
data = {
"requests": [
{
"appendCells": {
"sheetId": sheetId,
"rows": [ {"values": [
{"userEnteredValue": {"stringValue": value[0]}},
{"userEnteredValue": {"stringValue": value[1]}}
]}],
"fields" : "userEnteredValue"
}
}
]
}
res = SHEETS.spreadsheets().batchUpdate(spreadsheetId = SPREADSHEET_ID,body = data).execute()
values = ['O','P']
insertOneRecord(sheetId, values)
This is an extraction of a code I'm working on. Maybe it could be helpful:
appendResult = spreadsheet.append(
rangeName='%s!A3:D3' % title,
values=values,
valueInputOption="USER_ENTERED",
insertDataOption="INSERT_ROWS",
)
where title is the sheet Name, values is a tuple of tuples and spreadsheet is an instance of a custom class and the method append is this:
def append(self, rangeName, values, valueInputOption='RAW', insertDataOption='INSERT_ROWS'):
self._log.debug("Inserting values starting from %s/%s" % (self.spreadsheetId, rangeName))
return self._service.spreadsheets().values().append(
spreadsheetId=self.spreadsheetId,
range=rangeName,
valueInputOption=valueInputOption,
insertDataOption=insertDataOption,
body={
'values': values
}
).execute()
self._service is the same service you can see in Step 3 of Google Sheets API - Python Quick Start, as the result of discovery.build function.
make sure you follow all the OAuth consent.
https://developers.google.com/sheets/api/quickstart/js
The documentation regarding is here.
https://developers.google.com/sheets/api/samples/rowcolumn#append_empty_rows_or_columns
function InsertNewCol() {
var requests = [];
requests.push({
"appendDimension": {
"range": {
"dimension": "COLUMNS",
"length": 3
}
}
});
gapi.client.sheets.spreadsheets.batchUpdate({
spreadsheetId: 'YourSheetID',
requests: requests
}).then(function(response) {
console.log(response);
});}
I'm trying to create a wave robot, and I have the basic stuff working. I'm trying to create a new blip with help text when someone types #help but for some reason it doesnt create it. I'm getting no errors in the log console, and I'm seeing the info log 'in #log'
def OnBlipSubmitted(properties, context):
# Get the blip that was just submitted.
blip = context.GetBlipById(properties['blipId'])
text = blip.GetDocument().GetText()
if text.startswith('#help') == True:
logging.info('in #help')
blip.CreateChild().GetDocument().SetText('help text')
if it just started working, I have two suggestions...
-->Have you been updating the Robot Version in the constructor? You should change the values as you update changes so that the caches can be updated.
if __name__ == '__main__':
myRobot = robot.Robot('waverobotdev',
image_url = baseurl + 'assets/wave_robot_icon.png',
version = '61', # <-------------HERE
profile_url = baseurl)
-->The server connection between Wave and AppSpot has recently been extremely variable. Sometimes it takes 10+ minutes for the AppSpot server to receive my event, othertimes a few seconds. Verify you're receiving the events you expect.
Edit:
The code you provided looks good, so I wouldn't expect you're doing anything wrong in that respect.
Have you tried using Append() instead of SetText()? That's what I'd do in my C# API - I haven't used the Python API, but I'd imagine it's similar. Here's a sample from my demo robot:
protected override void OnBlipSubmitted(IEvent e)
{
if (e.Blip.Document.Text.Contains("robot"))
{
IBlip blip = e.Blip.CreateChild();
ITextView textView = blip.Document;
textView.Append("Are you talking to me?");
}
}
That works fine.
EDIT: Here's the resulting JSON from the above code:
{
"javaClass": "com.google.wave.api.impl.OperationMessageBundle",
"version": "173784133",
"operations": {
"javaClass": "java.util.ArrayList",
"list": [
{
"javaClass": "com.google.wave.api.impl.OperationImpl",
"type": "BLIP_CREATE_CHILD",
"waveId": "googlewave.com!w+PHAstGbKC",
"waveletId": "googlewave.com!conv+root",
"blipId": "b+Iw_Xw7FCC",
"index": -1,
"property": {
"javaClass": "com.google.wave.api.impl.BlipData",
"annotations": {
"javaClass": "java.util.ArrayList",
"list": []
},
"lastModifiedTime": -1,
"contributors": {
"javaClass": "java.util.ArrayList",
"list": []
},
"waveId": "googlewave.com!w+PHAstGbKC",
"waveletId": "googlewave.com!conv+root",
"version": -1,
"parentBlipId": null,
"creator": null,
"content": "\nAre you talking to me?",
"blipId": "410621dc-d7a1-4be5-876c-0a9d313858bb",
"elements": {
"map": {},
"javaClass": "java.util.HashMap"
},
"childBlipIds": {
"javaClass": "java.util.ArrayList",
"list": []
}
}
},
{
"javaClass": "com.google.wave.api.impl.OperationImpl",
"type": "DOCUMENT_APPEND",
"waveId": "googlewave.com!w+PHAstGbKC",
"waveletId": "googlewave.com!conv+root",
"blipId": "410621dc-d7a1-4be5-876c-0a9d313858bb",
"index": 0,
"property": "Are you talking to me?"
}
]
}
}
How does that compare with the JSON which comes out of your robot?
For some reason it just started working. I think the google wave is spotty.