If you simply rename the file, this will disrupt the loading mechanism. In addition, sometimes you can save a file under a different name than the original. Suppose you have the following model:
db.define_table("files", Field("name", unique=True), Field("file", "upload"))
You need to expand the download field with custom storage and retrieval features:
Field("file", "upload", custom_store=store_file, custom_retrieve=retrieve_file)
Functions just write / read a file from a fixed download 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 change form.vars before inserting / updating the database and set the file name. If you want to keep the original name of the downloaded file, this is optional.
def validate(form):
You also need to define a function to load the file, since the assembly in response.download will not 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 create a form. In the example below, I use the new mesh mechanism, which is much better than the old school uniforms (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 need any attachment to 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}