forked from lisaberrygis/AliasUpdater
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFeatureServiceAliasAndDescriptionUpdate_mulitipleLayers.py
243 lines (220 loc) · 14.4 KB
/
FeatureServiceAliasAndDescriptionUpdate_mulitipleLayers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# Name: Alias Updater
# Created by: Lisa Berry, Esri
# Created: December 2018
# Updated: June 2024
#
# This script uses a lookup table to update alias names on a hosted feature service.
# The script updates the alias names in two places:
# - The REST endpoint
# - The layer's pop-up JSON via fieldInfos
# - *If the layer was saved in the new Map Viewer in ArcGIS Online, updates the additional popupElement fieldInfos
# The pop-up configuration will not be altered with this implementation
# The script also allows you to update the long description, field type, and pop-up decimals/thousand separator for any field
#
# The script will use the input excel document and update any fields it finds that matches from the excel document.
# This script allows for multiple REST layers to be updated. Specify the REST layer count in the inputs.
# You must have ArcGIS Pro installed on your computer in order to run this script.
#
# Python version: 3.7 - Make sure your interpreter is calling to the arcgispro-py3 python.exe
# Updated: April 2020 - all http calls removed and replaced with python API calls
# Updated: July 2022 - Converted XLRD to OPENPYXL to read in excel file. XLRD no longer supports .xlsx files.
# Updated: August 2022 - can also update decimals for popup JSON.
# Also updates popupElement in JSON if saved in new Map Viewer
# Updated: August 2023 - no longer need to input layer count, which is determined automatically. Also, blank values
# in the excel doc are now handled by checking if they exist first, fixing NoneType error
# NOTE: As of 6/24, the script will alert you if you try to pass a long description with a < or > character. This will
# not run as expected since the REST API cannot pass the characters to the service.
# Comments about inputs:_________________________________________________________________________________________
# username and password are your ArcGIS Online or ArcGIS Enterprise credentials
#
# layerID is the ID to a hosted feature service.
# *** You must own the service to run this script.
#
# lookupTable must be an excel document (.xlsx) with a header row.
# The first column should be the field names
# The second column should be the intended alias names for each field.
# *optional* The third column should be the intended description for each field.
# *optional* The fourth column can include the field type. This must be formatted
# to match the backend JSON.
# Ex: nameOrTitle, description, typeOrCategory, countOrAmount, percentageOrRatio
# measurement, currency, uniqueIdentifier, phoneNumber, emailAddress,
# orderedOrRanked, binary, locationOrPlaceName, coordinate, dateAndTime
# *optional* The fifth column can include a specification for how many decimals you want for each field
# to have in the pop-up.
# *optional* The sixth column can include a specification for if a numeric attribute should have a thousands comma
# separator. Only specify this if it is a numeric field.
# Ex: can use "true" or "yes" to specify. You can leave this column blank for any fields that are string
# or don't need a comma. You can also specify those as "no" or "false".
#
# If your script is having issues, make sure you at least have these 5 headers in the excel document,
# even if no values appear in the rows. This can cause the script to fail sometimes. Also make sure your excel file is closed.
# portalName can be left as-is if you are working in ArcGIS Online. Change to your portal URL otherwise.
# Inputs:_______________________________________________________________________________________________________
username = "username"
password = "password"
layerID = "itemID"
lookupTable = r"C:\path\exceldocname.xlsx"
portalName = "https://www.arcgis.com"
# MAIN SCRIPT___________________________________________________________________________________________________
from arcgis import gis
from arcgis.features import FeatureLayer
import openpyxl
from copy import deepcopy
import os
import copy
# Login to your arcgis account
login = gis.GIS(portalName, username, password)
# Get layer count from service
updateItem = gis.Item(login, itemid=layerID)
restLayerCount = len(updateItem.layers)
# format the path to the excel document so it is recognized as a path
lookupTable = os.path.normpath(lookupTable)
# Read the lookup table and store the fields and alias names
if lookupTable[-4:] != "xlsx":
print("Please check your input. It needs to be a .xlsx excel file")
else:
print("Grabbing field and alias names from excel document...")
# Open Master Metadata excel document
workbook = openpyxl.load_workbook(lookupTable)
sheet = workbook.active
# Create an empty list to store all fields and alias names
lookupList = []
# Store values from master metadata excel doc and put into a list
iter = sheet.iter_rows()
iter.__next__()
for row in iter:
innerList = []
for val in row:
innerList.append(val.value)
lookupList.append(innerList)
looper = 0
while restLayerCount > 0:
# Access the feature layer intended for updating
search = login.content.search("id:" + layerID, item_type="Feature Layer")
featureLayer = FeatureLayer.fromitem(search[0], layer_id=looper)
layerName = search[0].name
print("Updating layer " + str(looper) + " on " + str(layerName) + "...")
print("\tGetting field definitions from service...")
# Loop through fields in service and store JSON for any that are going to be updated
layerFields = featureLayer.manager.properties.fields
print("\tFinding fields to update...")
# Loop through the fields in the service
updateJSON = []
for field in layerFields:
fieldName = field['name']
for lookupField in lookupList:
# As you loop through the service fields, see if they match a field in the excel document
if lookupField[0] == fieldName:
# store the field JSON from the online layer
fieldJSON = dict(deepcopy(field))
# assign the new alias name in JSON format
if lookupField[1]:
alias = lookupField[1]
fieldJSON['alias'] = alias
else:
alias = ""
# Assign field type, if specified
if lookupField[3]:
fldType = lookupField[3]
else:
fldType = ""
# assign the new field description in JSON format, if specified
if lookupField[2]:
longDesc = lookupField[2]
# Remove escape characters like double quotes, newlines, or encoding issues
if "<" or ">" in longDesc:
print("Special character > or < found in field: " + fieldName)
print("Script will not run as expected. Please remove all hyperlinks or > < characters from your long description and rerun the script.")
longDesc = longDesc.replace('"', '\\\"').replace("\n", " ").replace("\t", " ").replace(u'\xa0', u' ').replace(">=", " greater than or equal to ").replace("<=", " less than or equal to ").replace(">", " greater than ").replace("<", " less than ")
else:
longDesc = ""
# Build the JSON structure with the proper backslashes and quotes
fieldJSON['description'] = "{\"value\":" + "\"" + longDesc + "\"" + ",\"fieldValueType\":\"" + fldType + "\"}"
fieldJSON.pop('sqlType')
if alias != "":
print("\t\tField '" + fieldName + "' will be updated to get the alias name '" + alias + "'")
if longDesc != "":
print("\t\t\t\tThe long description for this field was also updated")
if fldType != "":
print("\t\t\t\tThe field type for this field was also updated to: " + fldType)
# Create a python list containing any fields to update
updateJSON.append(fieldJSON)
if updateJSON:
print("\tUpdating alias names of the REST service...")
#jsonFormat = json.dumps(updateJSON)
aliasUpdateDict = {'fields': updateJSON}
#aliasUpdateJSON = json.dumps(aliasUpdateDict)
# Use the update definition call to push the new alias names into the service
featureLayer.manager.update_definition(aliasUpdateDict)
print("\tAlias names updated on service!")
# Now check if the item has a pop-up configuration saving the alias names as well
# First, grab the item JSON for the layer and create an item to hold the new edited JSON
print("\tUpdating the alias names within the pop-up configuration on the item...")
item = gis.Item(login, itemid=layerID)
# Grab the existing JSON for the popup, store a copy, and edit the aliases
itemJSON = item.get_data(try_json=True)
# Loop through the existing layer and check if any alias names don't match
counter = 0
if itemJSON:
print("\tFinding all replacements of alias names within pop-up...")
newItemJSON = copy.deepcopy(itemJSON)
print("\t\tUpdating alias names in popup fieldInfos...")
for i in itemJSON['layers'][looper]['popupInfo']['fieldInfos']:
fieldName2 = i['fieldName']
for lookup in lookupList:
if lookup[0] == fieldName2:
if lookup[1] != None:
newItemJSON['layers'][looper]['popupInfo']['fieldInfos'][counter]['label'] = lookup[1]
# Check if there is a decimal spec
if "format" in i and "places" in i["format"]:
# If a value is specified in the lookup doc, assign that
if lookup[4] != None:
newItemJSON['layers'][looper]['popupInfo']['fieldInfos'][counter]['format']['places'] = lookup[4]
# If a value is not specified and the decimals have defaulted to 6, change to 2
else:
if newItemJSON['layers'][looper]['popupInfo']['fieldInfos'][counter]['format']['places'] == 6:
newItemJSON['layers'][looper]['popupInfo']['fieldInfos'][counter]['format']['places'] = 2
# Update thousands separator if lookup document specifies and if it exists in JSON
if lookup[5] != None and str(lookup[5]).lower() != "no" and str(lookup[5]).lower() != "false" and "format" in i and "digitSeparator" in i["format"]:
newItemJSON['layers'][looper]['popupInfo']['fieldInfos'][counter]['format']['digitSeparator'] = True
counter += 1
# Check if layer was updated in new Map Viewer and contains a popupElement JSON section with fieldInfos
if "popupElements" in itemJSON['layers'][looper]['popupInfo'] and itemJSON['layers'][looper]['popupInfo']["popupElements"]:
c = 0
for i in itemJSON['layers'][looper]['popupInfo']["popupElements"]:
if i['type'] == 'fields':
print("\t\tUpdating popupElement fieldInfo...")
counter2 = 0
if "fieldInfos" in itemJSON['layers'][looper]['popupInfo']["popupElements"][c]:
for j in itemJSON['layers'][looper]['popupInfo']["popupElements"][c]["fieldInfos"]:
fldName = j["fieldName"]
for lkup in lookupList:
if lkup[0] == fldName:
if lkup[1] != None:
newItemJSON['layers'][looper]['popupInfo']['popupElements'][c]["fieldInfos"][counter2]['label'] = lkup[1]
# Check if there is a decimal spec
if "format" in j and "places" in j["format"]:
# If a value is specified in the lookup doc, assign that
if lkup[4] != None:
newItemJSON['layers'][looper]['popupInfo']['popupElements'][c]["fieldInfos"][counter2]['format']['places'] = lkup[4]
# If a value is not specified and the decimals have defaulted to 6, change to 2
else:
if newItemJSON['layers'][looper]['popupInfo']['popupElements'][c]["fieldInfos"][counter2]['format']['places'] == 6:
newItemJSON['layers'][looper]['popupInfo']['popupElements'][c]["fieldInfos"][counter2]['format']['places'] = 2
# Update thousands separator if lookup document specifies and if it exists in JSON
if lkup[5] != None and str(lkup[5]).lower() != "no" and str(lkup[5]).lower() != "false" and "format" in j and "digitSeparator" in j["format"]:
newItemJSON['layers'][looper]['popupInfo']['popupElements'][c]["fieldInfos"][counter2]['format']['digitSeparator'] = True
counter2 += 1
c += 1
# Update json
print("\tUpdating the alias names within the existing item pop-up...")
portal = portalName
update = item.update(item_properties={'text': newItemJSON})
if update:
print("\tSuccess! Your alias names have been updated. Please check your service to confirm.")
else:
print("\tUpdating pop-up failed.")
else:
print("\tNo pop-up JSON. Skipping.")
looper += 1
restLayerCount -= 1