I have a webtool (geoprocessing service) that generates a file at the end of its workflow.
How do I get it to automatically open the "download" window so that I can retrieve the file?
Thanks!
Solved! Go to Solution.
Well, this is both annoying and embarrassing.
As it turns out, the error lay not in anything crazy with permissions, but in the custom report name. One of the fields used in the report name (${casetype}) has values that contain spaces. So the report named "WYWY123456789__Telephone Lines.pdf" becomes "WYWY123456789__Telephone%20Lines.pdf" when output as a URL. However, there isn't any file named "WYWY123456789__Telephone%20Lines.pdf", so the url doesn't work.
Changing the report title scheme fixed the problem.
The other thing I found is that the step of copying the report seems to be load-bearing. I removed it after I got things working originally and it appears that the output was invalid unless you also copy.
In answer to my original question, setting the output to a DOCX will prompt that download window. I assume it's also the case for a zip file. PDFs like to open in the browser.
The final question that this workflow leaves is whether I need to somehow clear out the output folder somehow, but I feel like that's a problem for another day.
Thanks for the help everyone.
Edit: Here is my solution to get rid of those pesky bad characters by reversing the URL and whitelisting valid characters
import arcpy
import arcgis
import os
import shutil
import urllib
class reportGenerator:
def __init__(self):
"""Define the tool (tool name is the name of the class)."""
self.label = "Report Generator"
self.description = ""
def getParameterInfo(self):
"""Define the tool parameters."""
inFeature = arcpy.Parameter(displayName='Input Feature',
name='inFeature',
datatype='GPFeatureRecordSetLayer',
parameterType='Required',
#multiValue = True,
direction='Input')
outFile = arcpy.Parameter(displayName='Output file',
name='outFile',
datatype='DEFile',
parameterType='Optional',
#multiValue = True,
direction='Output')
outFile.enabled = False
outType = arcpy.Parameter(displayName='Output file format',
name='outType',
datatype='GPString',
parameterType='Required',
#multiValue = True,
direction='Input')
outType.filter.list = ["docx", "pdf"]
outType.value = "pdf"
#
params = [
inFeature,
outFile,
outType
]
return params
def isLicensed(self):
return True
def updateParameters(self, params):
return
def updateMessages(self, params):
return
def execute(self, params, messages):
"""The source code of the tool."""
def main(oids: list, outType: str)-> None:
'''
Takes the input features and exports them to a feature
report in either PDF or Word format.
:param oids: List of objectids to export
:param outType: Output file format. Either PDF or DOCX.
:return: None
'''
scratchenv = arcpy.env.scratchFolder
gis = arcgis.gis.GIS("home")
sman = arcgis.apps.survey123.SurveyManager(gis)
sID = sman.get('YourFormIDHere')#Form Item itemID.
templates = sID.report_templates
# Set the request message
oids = [str(o) for o in oids]
oids = ", ".join(oids)
where_clause = f"objectid in ({oids})"
# No case type because they contain spaces and that breaks
# the URL.
reportTitle = "${cse_nr}-${leg_cse_nr}${solotpass}${preappname}"#${inspecdate}"
# Generate the report(s)
pdf_report = sID.generate_report(templates[0],
where= where_clause,
output_format=outType,#)
report_title = reportTitle)
''' Copy to the scratch folder
This is load-bearing and must be done.
'''
# Get rid of all bad characters by reverse-engineering the
# url and then passing the name through the whitelist
validchars = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D',
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8',
'9', '0', "_", "-", "."]
out_name = os.path.basename(str(pdf_report))
out_name = urllib.parse.unquote(out_name)
out_pdf = ""
for char in out_name:
if char in validchars:
out_pdf += char
out_pdf = out_pdf.strip("-_")
out_pdf = os.path.join(scratchenv, out_pdf)
shutil.copy(pdf_report, out_pdf)
arcpy.AddMessage(f"Successfully exported. "
f"Check the parameters to download.")
# Set the output parameter to the address so that they can
# find it easily.
arcpy.SetParameter(1, out_pdf)
return
if __name__ == "__main__":
# return
# Set everything to a Featureset to work in Portal.
inFeature = getPName(params, "inFeature").value
inFeature = arcpy.FeatureSet(inFeature)
geoJSON = json.loads(inFeature.JSON)
totCount = arcpy.management.GetCount(r"https://pathtofeatureservice")
totCount = int(totCount[0])
oids = [f["attributes"]["objectid"] for f in geoJSON["features"]]
# Webtools aren't aware of map selections so this checks to
# see if you actually made one.
if len(oids) == totCount:
arcpy.AddWarning("No selection was made. Tool not run.")
return
outType = getPName(params, "outType").value
main(oids, outType)
return
def postExecute(self, params):
returnI haven't thought of a good way to format any date fields in a way I like (They print to March 6, 2026 and I'd like 20260306) but the good news is this appears to export all my records just fine.
I've had success with making the file an output parameter, this outputs a URL in the old Web AppBuilder geoprocessing widget (I hope the ExB widget does the same thing, can't test that yet) and if you're doing manual HTTP work you should get the URL somewhere in the response data (sync) or you follow the URL trail from the final status update (async). What's the client you're using to run the tool?
Sooooo, I'm running this through Experience Builder on Enterprise.
I do get a url in the output parameter, but it goes to someplace that doesn't exist when I open it.
My tool is an attempt to get around the various issues I've been having with the feature report widget. By leaving the "folder_id" parameter blank for generate function, it allegedly should go to the scratch space by default? The output URLs seem to point to a scratch space, although I get a 400 error.
https://.../hosted/rest/directories/arcgisjobs/test_report_generator_gpserver/.../scratch/myreport.pdf
I'm trying to ask a colleague how he got his to work, but he's out of the office and also his tool works primarily via AGOL, so even though it may truly live on Enterprise, that might change things.
I know his tool uses the scratch environment (arcpy.env.scratchFolder).
I guess a potential workaround to try might be to like, manually copy the output to "scratch environment" and return that? Idk.
In any case, I think it'd be useful to force the download window open so that my users actually retrieve the report.
I double-checked a few of my tools and yes, you want to get your file into arcpy.env.scratchFolder and then set a derived File output parameter to the full path to that file. I forget exactly how to configure the parameter during publishing but I don't remember any gotchas. You can even leverage the zipfile module to write your output(s) into a nice little file for the user.
We have a geoprocessing service that outputs a pdf and/or a zip file, depending on the users needs. We send Survey123 records to the gp service from a Web AppBuilder application in ArcGIS Online. We return url's for the outputs to the web application, and the user clicks on the url's to pop open a new browser tab where the pdf/zip can be viewed/downloaded. I assume we could host the gp service on our Enterprise and have it configured identically. I don't think the web application "cares" where the gp service is running.
The gp service uses the ScratchFolder for outputs. When run locally during development the output is in some temp scratch folder in the user profile. When published to the server as a gp service the output adjusts to become the job scratch folder.
Here is an example output url, minus a few details specific to our implementation, showing the service job id and scratch in the path.
Screenshot of the application with a url returned from the process for customer use
Our tool in Pro is configured like this (screenshot)
Hope some of that might be useful.
I feel like I have to be missing something pretty basic here.
Here is the tool from the pyt. (If I'm missing an import just assume that it's actually there).
import arcgis
import arcpy
import os
import shutil
class lafiReportGenerator:
def __init__(self):
"""Define the tool (tool name is the name of the class)."""
self.label = "Report Generator"
self.description = ""
def getParameterInfo(self):
"""Define the tool parameters."""
inFeature = arcpy.Parameter(displayName='Input Feature',
name='inFeature',
datatype='GPFeatureRecordSetLayer',
parameterType='Required',
#multiValue = True,
direction='Input')
outFile = arcpy.Parameter(displayName='Output file',
name='outFile',
datatype='DEFile',
parameterType='Optional',
#multiValue = True,
direction='Output')
outFile.enabled = False
outType = arcpy.Parameter(displayName='Output file format',
name='outType',
datatype='GPString',
parameterType='Required',
direction='Input')
outType.filter.list = ["docx", "pdf"]
outType.value = "pdf"
params = [
inFeature,
outFile,
outType
]
# params = None
return params
def isLicensed(self):
"""Set whether the tool is licensed to execute."""
return True
def updateParameters(self, params):
"""Modify the values and properties of parameters before internal
validation is performed. This method is called whenever a parameter
has been changed."""
return
def updateMessages(self, params):
"""Modify the messages created by internal validation for each tool
parameter. This method is called after internal validation."""
return
def execute(self, params, messages):
"""The source code of the tool."""
def main(oids, outType):
scratchenv = arcpy.env.scratchFolder
gis = arcgis.gis.GIS("home")
sman = arcgis.apps.survey123.SurveyManager(gis)
sID = sman.get('ItemIDHere')#Form Item itemID.
templates = sID.report_templates
oids = [str(o) for o in oids]
oids = ", ".join(oids)
where_clause = f"objectid in ({oids})"
reportTitle = "${cse_nr}_${leg_cse_nr}_${solotpass}_${casetype}"
pdf_report = sID.generate_report(templates[0],
where= where_clause,
output_format=outType,#)
report_title = reportTitle)
out_pdf = os.path.join(scratchenv,
os.path.basename(str(pdf_report)))
shutil.copy(pdf_report, out_pdf)
arcpy.AddMessage(out_pdf)
arcpy.AddMessage(f"Successfully exported. "
f"Check the parameters to download.")
arcpy.SetParameter(1, out_pdf)
return
if __name__ == "__main__":
# return
inFeature = getPName(params, "inFeature").value
inFeature = arcpy.FeatureSet(inFeature)
geoJSON = json.loads(inFeature.JSON)
totCount = arcpy.management.GetCount(r"https://myservicethat thesurveybelongsto/FeatureServer/0")
totCount = int(totCount[0])
oids = [f["attributes"]["objectid"] for f in geoJSON["features"]]
if len(oids) == totCount:
arcpy.AddWarning("No selection was made. Tool not run.")
return
outType = getPName(params, "outType").value
main(oids, outType)
return
def postExecute(self, params):
"""This method takes place after outputs are processed and
added to the display."""
returnIt works great in Pro. In EB I run the tool, it runs successfully, and I get a link of some sort in the output parameter. Clicking on the link sends me to a 400 error as I showed in my earlier post.
Good news is I copied the GP service to AGOL and it failed there too.
So, I'm reasonably sure this is user error on my end but I can't figure out what.
Well, this is both annoying and embarrassing.
As it turns out, the error lay not in anything crazy with permissions, but in the custom report name. One of the fields used in the report name (${casetype}) has values that contain spaces. So the report named "WYWY123456789__Telephone Lines.pdf" becomes "WYWY123456789__Telephone%20Lines.pdf" when output as a URL. However, there isn't any file named "WYWY123456789__Telephone%20Lines.pdf", so the url doesn't work.
Changing the report title scheme fixed the problem.
The other thing I found is that the step of copying the report seems to be load-bearing. I removed it after I got things working originally and it appears that the output was invalid unless you also copy.
In answer to my original question, setting the output to a DOCX will prompt that download window. I assume it's also the case for a zip file. PDFs like to open in the browser.
The final question that this workflow leaves is whether I need to somehow clear out the output folder somehow, but I feel like that's a problem for another day.
Thanks for the help everyone.
Edit: Here is my solution to get rid of those pesky bad characters by reversing the URL and whitelisting valid characters
import arcpy
import arcgis
import os
import shutil
import urllib
class reportGenerator:
def __init__(self):
"""Define the tool (tool name is the name of the class)."""
self.label = "Report Generator"
self.description = ""
def getParameterInfo(self):
"""Define the tool parameters."""
inFeature = arcpy.Parameter(displayName='Input Feature',
name='inFeature',
datatype='GPFeatureRecordSetLayer',
parameterType='Required',
#multiValue = True,
direction='Input')
outFile = arcpy.Parameter(displayName='Output file',
name='outFile',
datatype='DEFile',
parameterType='Optional',
#multiValue = True,
direction='Output')
outFile.enabled = False
outType = arcpy.Parameter(displayName='Output file format',
name='outType',
datatype='GPString',
parameterType='Required',
#multiValue = True,
direction='Input')
outType.filter.list = ["docx", "pdf"]
outType.value = "pdf"
#
params = [
inFeature,
outFile,
outType
]
return params
def isLicensed(self):
return True
def updateParameters(self, params):
return
def updateMessages(self, params):
return
def execute(self, params, messages):
"""The source code of the tool."""
def main(oids: list, outType: str)-> None:
'''
Takes the input features and exports them to a feature
report in either PDF or Word format.
:param oids: List of objectids to export
:param outType: Output file format. Either PDF or DOCX.
:return: None
'''
scratchenv = arcpy.env.scratchFolder
gis = arcgis.gis.GIS("home")
sman = arcgis.apps.survey123.SurveyManager(gis)
sID = sman.get('YourFormIDHere')#Form Item itemID.
templates = sID.report_templates
# Set the request message
oids = [str(o) for o in oids]
oids = ", ".join(oids)
where_clause = f"objectid in ({oids})"
# No case type because they contain spaces and that breaks
# the URL.
reportTitle = "${cse_nr}-${leg_cse_nr}${solotpass}${preappname}"#${inspecdate}"
# Generate the report(s)
pdf_report = sID.generate_report(templates[0],
where= where_clause,
output_format=outType,#)
report_title = reportTitle)
''' Copy to the scratch folder
This is load-bearing and must be done.
'''
# Get rid of all bad characters by reverse-engineering the
# url and then passing the name through the whitelist
validchars = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D',
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8',
'9', '0', "_", "-", "."]
out_name = os.path.basename(str(pdf_report))
out_name = urllib.parse.unquote(out_name)
out_pdf = ""
for char in out_name:
if char in validchars:
out_pdf += char
out_pdf = out_pdf.strip("-_")
out_pdf = os.path.join(scratchenv, out_pdf)
shutil.copy(pdf_report, out_pdf)
arcpy.AddMessage(f"Successfully exported. "
f"Check the parameters to download.")
# Set the output parameter to the address so that they can
# find it easily.
arcpy.SetParameter(1, out_pdf)
return
if __name__ == "__main__":
# return
# Set everything to a Featureset to work in Portal.
inFeature = getPName(params, "inFeature").value
inFeature = arcpy.FeatureSet(inFeature)
geoJSON = json.loads(inFeature.JSON)
totCount = arcpy.management.GetCount(r"https://pathtofeatureservice")
totCount = int(totCount[0])
oids = [f["attributes"]["objectid"] for f in geoJSON["features"]]
# Webtools aren't aware of map selections so this checks to
# see if you actually made one.
if len(oids) == totCount:
arcpy.AddWarning("No selection was made. Tool not run.")
return
outType = getPName(params, "outType").value
main(oids, outType)
return
def postExecute(self, params):
returnI haven't thought of a good way to format any date fields in a way I like (They print to March 6, 2026 and I'd like 20260306) but the good news is this appears to export all my records just fine.