I was looking over the documentation for the supported operations of the FeatureService and noticed that starting at 10.4, the applyEdits method started supporting an "attachments" parameter. I am following the documentation and cannot get this to work by doing it manually against the REST endpoint, or programmatically with Python.
I cannot see where I'm going wrong, I have tried to do both add and delete using the apply edits and neither one has worked. I am using this test feature layer (open access):
The documentation has this as an example for the attachments parameter:
{
"adds": [{
"globalId": "{55E85F98-FBDD-4129-9F0B-848DD40BD911}",
"parentGlobalId": "{02041AEF-4174-4d81-8A98-D7AC5B9F4C2F}",
"contentType": "image/pjpeg",
"name": "Pothole.jpg",
"uploadId": "{DD1D0A30-CD6E-4ad7-A516-C2468FD95E5E}"
},
{
"globalId": "{3373EE9A-4619-41B7-918B-DB54575465BB}",
"parentGlobalId": "{6FA4AA68-76D8-4856-971D-B91468FCF7B7}",
"contentType": "image/pjpeg",
"name": "Debree.jpg",
"data": "<base 64 encoded data>"
}
],
"updates": [{
"globalId": "{8FDD9AEF-E05E-440A-9426-1D7F301E1EBA}",
"contentType": "image/pjpeg",
"name": "IllegalParking.jpg",
"uploadId": "{57860BE4-3B85-44DD-A0E7-BE252AC79061}"
}],
"deletes": [
"{95059311-741C-4596-88EF-C437C50F7C00}",
" {18F43B1C-2754-4D05-BCB0-C4643C331C29}"
]
}
I have tried doing both update and add to no avail. The features have attachments related by GlobalId, and the "useGlobalIds" parameter is set to "true" when I try it. When I do the "adds", I am not including a GlobalId because I'm assuming those will be generated automatically, however, I am including the parentGlobalId and have encoded the image in base 64.
When the adds failed, I thought maybe it was because I was not including a globalId for the new feature, so I tried an update which would have an existing global id. This is what I passed into the "attachments" parameter for an update:
{
"parentGlobalId": "266f0b2c-88c2-4ac7-8e40-10ff461279dc",
"globalId": "a91a085b-e30c-4146-af96-eb792c6269b5"
"data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABNoA...", //the rest is ommitted for clarity
"contentType": "image/png",
"name": "routing.png",
}
When trying this with Python, I use Fiddler to watch my request to make sure they are correctly formatted and the screenshot below shows the other parameters I'm using but the attachments parameter does not show up all the way because Fiddler truncates the value which is massive in this case due to the base64 encoded image:
the updates call does work when I do not try to add the attachments parameter. Has anyone had success using the attachments parameter in apply edits? I know I can add/update/delete attachments one at a time, but it would be nice how to figure out how to batch edit attachments in the applyEdits method.
It's the addAttachment method. applyEdits is for changing features. Have you tried adding the file to body? When I used RestSharp, I had to pass the file into AddFile instead of AddParameter.
Did you ever figure out how to use applyEdits to add or update attachments?
Thanks!
No, per my original reply, you cannot use applyEdits to add or update attachments. applyEdits is for features.
You need to use the addAttachment method to add a new attachment.
Along similar lines, I have a feature class with attachments, but sometimes the attachments get linked to the wrong feature. Is there a way to change the link from the attachment to the feature, or am I going to have to remove the attachments and reattach them to the correct feature? My understanding is it's just a related 1:many relationship on the back end, so it should just be an issue of updating the child ID, but I can't figure out how to do this. I have limited coding experience, so that may be why.
I know it's been a while but I still wanted to share it for someone who's stuck with it. Here's my code, written by C#, using HttpClient.
The linkAddAttachment parameter has the following format: https://<root>/<serviceName>/FeatureServer/<layerId>/<featureId>/addAttachment
For more information: https://developers.arcgis.com/rest/services-reference/enterprise/add-attachment/
private async Task<string> addAttachment(string attachmentPath, string attachmentType, string attachmentName, string linkAddAttachment)
{
try
{
// get token from portal
string token = await _singleSignOnHelper.GetTokenFromPortal(_appSettings.userAdmin, _appSettings.passAdmin);
if (token.Trim().Length > 0)
{
FileStream fileStream = new FileStream(attachmentPath, FileMode.Open);
using var content = new MultipartFormDataContent();
var fileContent = new StreamContent(fileStream);
fileContent.Headers.ContentType = new MediaTypeHeaderValue(attachmentType);
content.Add(
content: fileContent,
name: "\"attachment\"",
fileName: attachmentName);
content.Add(new StringContent("pjson"), "f");
using var request = new HttpRequestMessage(HttpMethod.Post, linkAddAttachment);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Content = content;
var response = await _httpClient.SendAsync(request);
var responseStatusCode = response.StatusCode;
if (responseStatusCode.ToString() == "OK")
{
var responseBody = await response.Content.ReadAsStringAsync();
if (responseBody == "[]")
{
responseBody = "";
}
return await Task.FromResult(responseBody);
}
else
{
return "";
}
}
else
{
return "";
}
}
catch (Exception ex)
{
return "";
}
}
Hi all, recently I was trying to do this using the GIS API for Python, and after tinkering for a bit, I managed to get it to work!
Here's a code snippet of what I did:
# Represents details of a feature I'll be modifying attachments for [Add]
feat = {
'globalid': '{d1109cda-3cb3-4ede-81ed-0809a1a34bda}'
}
# Attachments object
attachments_obj = {
'adds': [],
}
def open_file_as_base_64(image_path):
data = None
with open(image_path, 'rb') as img:
data = base64.b64encode(img.read())
img.close()
return data
# Iterate file details on a list of dictionaries that represent several properties of the files I want to add.
for file in list_of_dictionaries_containing_file_details:
file_name = os.path.basename(file['file_path'])
extension = os.path.splitext(file_name.lower())[1].replace('.', '')
# Used for the contentType parameter
if(extension == 'jpg'):
extension = "jpeg"
elif (extension == 'png'):
pass
elif (extension == 'tif'):
extension = 'tiff'
b64_data = open_file_as_base_64(file['file_path'])
attachments_obj['adds'].append({
'globalId': f'{{{uuid.uuid4()}}}',
'parentGlobalId': feat['globalid'],
'name': file_name,
'contentType': f"image/{extension}",
'data': str(b64_data) # Convert bytes to string
})
del b64_data
fl.edit_features(use_global_ids=True, attachments=attachments_obj)
Notice I had to create a globalId for the attachments to be added. I also had to encode image as base64 and then convert that output to string. I imagine something like this must be done if using purely the REST API, and I assume it'll work because the GIS API for Python is just a wrapper for the rest API :^]
Best of luck!