Update a Hosted Field Domain with the Python API

557
0
06-14-2022 07:44 AM
jcarlson
MVP Esteemed Contributor
0 0 557

Has this ever happened to you? You go to edit a field's List (AGOL / Enterprise term for a domain), and you get this message:

jcarlson_0-1655215293750.png

Not the most helpful of error messages, but if we take a look at our browser Developer Tools, we can see this response:

{
"error": {
"code":500,
"message":"JSONObject[\"description\"] is not a string.",
"details":[]
}
}

What's going on here? I can see that the description is null in the payload sent to the server:

... "description":null, ... 

You could attempt to open the URL your request was sent to, edit the payload, and resubmit. But that feels a bit hacky.

There's something funny here, and a recent comment elsewhere led me to believe that this was a 10.9.1 issue. Regardless, I've got domains that do have descriptions, and I still get this message. Now, I could fix these domains by republishing the layer, but that's sort of like swatting a fly with a baseball bat. Some alternative method is needed.

 Let's Try it with Python

Whether you've attempted to alter a list using Python or not, know that it doesn't have to be hard. But the update_definition method can be finicky. I mean, even the Portal GUI can't update the list for me!

The key to a smooth operation is to modify as little of the existing definition as possible. Writing up your own JSON from scratch can lead to a lot of less-than-helpful error messages. Instead, we'll pull the JSON definition of our layer into a dict and make modifications to it there.

Personally, I think this makes a great interactive Notebook. Having those intermediate outputs is really helpful here.

Pull out the Service Definition JSON

 

fs = gis.content.get('02803529b103488fb5f75bbd6b1cfe9c').layers[0]
json = fs.properties

 

 

If you have a lot of fields, it can be helpful to see the list index of each with a quick dict comprehension.

 

{x['name']:json['fields'].index(x) for (x) in json['fields']}

 

 

Which looks like this:

{
'objectid': 0,
'globalid': 1,
'uniquerowid': 2,
'complaint_year': 3,
'investigation_number': 4,
'date_recorded': 5,
'complaint_source': 6,
'complaint_details': 7,
'status': 8,
...
}

 

Using the list index (8 in this example), we can pull out the domain to look at it.

 

json['fields'][8]

 

 

Which looks like:

{
'nullable': True,
'editable': True,
'defaultValue': None,
'domain': {'name': 'service_3742b925bb7947d486f585b6b3c5564b_cvd_status',
'type': 'codedValue',
'codedValues': [{'code': '2', 'name': 'Open'},
{'code': '1', 'name': 'Pending'},
{'code': '0', 'name': 'Closed'}]},
'name': 'status',
'alias': 'Status',
'type': 'esriFieldTypeInteger'
}

Make Changes 

Adding a New Value

Notices that the codedValues object is just a list, so we can use .append here. As long as we follow the same format for our new value, of course.

 

new_value = {'code': '3', 'name': 'Reopened for Review'}

json['fields'][8]['domain']['codedValues'].append(new_value)

 

 Changing a Value

Maybe you just need to correct a typo or something. Just reassign the value! If you need a reminder of the list index of your value (helpful when it's a long list), you can use another dict comprehension here.

 

json['fields'][8]['domain']['codedValues'][1]['name'] = 'Pending Appeal'

 

Removing a Value

Being a list, it's as simple as using .pop, together with the list index of the item you want removed.

 

json['fields'][8]['domain']['codedValues'].pop(3)

 

 Apply your Changes

We've modified our json object in place, changing only what needs changing. Passing it back to the server is simple:

 

fs.manager.update_definition(json)

 

 

That's it! Let me know if this doesn't work for you, or if you have other ways of doing it, I'd love to hear it.

About the Author
I'm a GIS Analyst for Kendall County, IL. When I'm not on the clock, you can usually find me contributing to OpenStreetMap, knitting, or nattering on to my family about any and all of the above.