If you simply rename the file, this will break the download mechanism. Moreover, sometimes you might want to save the file under a different name than the original. Let's assume you have the following model:
db.define_table("files",
Field("name", unique=True),
Field("file", "upload"))
You need to extend the upload field with customised store and retrieve functions:
Field("file", "upload", custom_store=store_file, custom_retrieve=retrieve_file)
The functions are simply writing/reading a file from a fixed upload directory:
import os
import shutil
def store_file(file, filename=None, path=None):
path = "applications/app_name/uploads"
if not os.path.exists(path):
os.makedirs(path)
pathfilename = os.path.join(path, filename)
dest_file = open(pathfilename, 'wb')
try:
shutil.copyfileobj(file, dest_file)
finally:
dest_file.close()
return filename
def retrieve_file(filename, path=None):
path = "applications/app_name/uploads"
return (filename, open(os.path.join(path, filename), 'rb'))
Now in the controller you need to modify the form.vars before the database insert/update is done and set the file name. If you want to keep the original name of the uploaded file, this is not necessary.
def validate(form):
# set the uploaded file name equal to a name given in the form
if form.vars.file is not None:
form.vars.file.filename = form.vars.name
You also need to define a function to download the file as the build in response.download won't work:
import contenttype as c
def download():
if not request.args:
raise HTTP(404)
name = request.args[-1]
field = db["files"]["file"]
try:
(filename, file) = field.retrieve(name)
except IOError:
raise HTTP(404)
response.headers["Content-Type"] = c.contenttype(name)
response.headers["Content-Disposition"] = "attachment; filename=%s" % name
stream = response.stream(file, chunk_size=64*1024, request=request)
raise HTTP(200, stream, **response.headers)
To connect the dots, you need to build the form. In the example below I'm using the new grid mechanism which is way better than the old school forms (but not yet documented in the book).
upload = lambda filename: URL("download", args=[filename])
def index():
grid = SQLFORM.grid(db.files, onvalidation=validate, upload=upload)
return {"grid":grid}
If you don't want all the fanciness of the grid, the equivalent controller code is:
def index():
if len(request.args):
form=SQLFORM(db.files, request.args[0], upload=URL("download"))
else:
form=SQLFORM(db.files, upload=URL("download"))
if form.process(onvalidation=validate).accepted:
response.flash = "files updated"
return {"form":form}