Hello,
I'm coming from web2py, and in my free time, I've been doing some research in anticipation of a future (and, I believe, distant) ☹ migration to py4web.
Right now, I'm working with upload fields and image downloads, and there's something that's not working quite right. If files are stored in `settings.UPLOAD_FOLDER`, everything works well.
However, one of the applications I have in web2py heavily uses upload fields with images. Because of this, I have a directory for each table within the "uploads" folder.
└── uploads
├── t_table_1
├── t_table_2
└── t_table_3
Inside each directory, I store the images from the upload fields of each table.
There are several things that I believe are not quite right.
• First, there's the action defined in common.py of the "_scaffold" app for downloading files from upload fields. The comments reference specifying "field.upload_path," but it seems like there's either an error or something pending implementation. Would it be appropriate to submit a PR to change it to "field.uploadfolder"?
# #######################################################
# Define a convenience action to allow users to download
# files uploaded and reference by Field(type='upload')
# #######################################################
if settings.UPLOAD_FOLDER:
@action('download/<filename>')
@action.uses(db)
def download(filename):
return downloader(db, settings.UPLOAD_FOLDER, filename)
# To take advantage of this in Form(s)
# for every field of type upload you MUST specify:
#
# field.upload_path = settings.UPLOAD_FOLDER
# field.download_url = lambda filename: URL('download/%s' % filename)
• Second, when I use uploadfolder = settings.MY_CUSTOM_UPLOAD_FOLDER in a field of a table, the proposed action in "_scaffold" for downloading files doesn't work, and the links to the files are broken (404 error).
Is this the expected behavior, and should new actions be defined to download files with custom upload folders?
While reviewing the code, I came across the following:
def downloader(db, path, filename, download_filename=None):
"""
Given a db, and filesystem path, and the filename of an uploaded file,
it retrieves the file, checks permission, and returns or stream its.
Optionally as an attachment if the URL contains attachment=true
If the file is not in the filesystem, it gets copied into the path folder
before being returned for speed.
To be used as follows:
@action('download/<filename>')
@action.uses(db)
def download(filename):
return downloader(db, PATH, filename)
PATH is the fullpath to where uploaded files are normally stored.
"""
items = re.match(REGEX_UPLOAD_PATTERN, filename)
if not items:
raise HTTP(404)
tablename = items.group("table")
fieldname = items.group("field")
try:
field = db[tablename][fieldname]
# New line
path = field.uploadfolder if field.uploadfolder else path
# Functionality to handle uploadseparate Field declaration.
if field.uploadseparate:
uuidname = items.group("uuidkey")
path = os.path.join(path, *[f"{tablename}.{fieldname}", uuidname[:2]])
except (AttributeError, KeyError):
raise HTTP(404)
try:
(original_name, stream) = field.retrieve(filename, path, nameonly=True)
fullpath = os.path.join(path, filename)
if not os.path.exists(fullpath) and hasattr(stream, "read"):
with open(fullpath, "wb") as fp:
shutil.copyfile(fp, stream)
except NotAuthorizedException:
raise HTTP(403)
except NotFoundException:
raise HTTP(404)
except IOError:
raise HTTP(404)
if not request.query.get("attachment"):
download_filename = None
elif not download_filename:
download_filename = original_name
return bottle.static_file(filename, root=path, download=download_filename)
I see that with just one line
path = field.uploadfolder if field.uploadfolder else path
it's easy to override the image path when "uploadfolder" is specified in the table field definition. This way, with just one "download" action, everything would work well.
I suppose it's up to Massimo to decide if these changes would be appropriate.
Carlos.