Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

web2py upload with original filename

I want to upload file with SQL.factory() I would just like to maintain the original filename my code currently is

form = SQLFORM.factory(
    Field('file_name', requires=IS_NOT_EMPTY()),
    Field('file', 'upload',uploadfolder=upload_folder))
if form.accepts(request.vars, session):  #.process().accepted:
    response.flash = u'File uploaded'
    session.your_name = form.vars.file_name
    session.filename = request.vars.file 
elif form.errors:
    response.flash = 'form has errors'
return dict(form=form)

I guess session.filename = request.vars.file is where you set file name. Why do i get the autogenerated file name no_data.smth.23u8o8274823zu4i2.smth

Thank you

like image 634
Yebach Avatar asked Nov 04 '11 10:11

Yebach


3 Answers

First, request.vars.file is a Python cgi.FieldStorage object, so session.filename = request.vars.file should result in an error. request.vars.file.file is the actual file object, and request.vars.file.filename is the original name of the uploaded file.

When you upload a file via an upload field, web2py automatically generates a new name of the form 'table_name.field_name.random_id.b16encoded_original_filename.extension'. This is done to prevent directory traversal attacks and to enable the download mechanism (which needs to know the table and field name). In the case of SQLFORM.factory, there is no database table name, so it defaults to a table name of 'no_table'.

The code you have shown should not actually generate a filename like 'no_data.smth.23u8o8274823zu4i2.smth'. That filename implies you have explicitly told SQLFORM.factory to use a table name of 'no_data' (via its table_name argument) and that the upload field name is 'smth'. (The code above would generate a filename starting with 'no_table.file'.)

Note, web2py automatically obtains the original name of the uploaded file and encodes it (using b16encode) into the new filename (it then decodes the original filename when the built-in download mechanism is used). The original filename is also available in form.vars.file.filename. So, you don't necessarily need the user to enter a filename at all. However, if you want to enable the user to enter a filename that may differ from the actual filename and then use the user-entered filename, you can add the following before the form creation:

if 'file' in request.vars and request.vars.file_name:
    request.vars.file.filename = request.vars.file_name

That will re-assign the filename of the uploaded file to the value entered by the user, and web2py will then encode that user-entered filename into the new filename. Note, however, that web2py relies on the filename extension to set the HTTP headers appropriately upon download, so you may want to add some logic to obtain the original filename extension in case the user fails to enter it.

like image 117
Anthony Avatar answered Nov 18 '22 02:11

Anthony


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}
like image 43
Wikus van de Merwe Avatar answered Nov 18 '22 02:11

Wikus van de Merwe


so i did it :) here is my code

import os
upload_folder ='C:\\Python27\\web2py'
sl = "\\"
path = upload_folder + sl

def display_form():

     form = SQLFORM.factory(
        Field('file_name', requires=IS_NOT_EMPTY()),
        Field('file', 'upload',uploadfolder=upload_folder))


     if form.accepts(request.vars, session):  #.process().accepted:
        session.file_name= form.vars.file_name
        coded_name = form.vars.file 
        orig_name = request.vars.file.filename
        os.rename(path + coded_name, path + orig_name)
        response.flash = u'datoteka naložena'

    elif form.errors:
        response.flash = 'form has errors'
    return dict(form=form)

I know it is probably not the best solution but since it works, I like :)

thank you Anthony

like image 2
Yebach Avatar answered Nov 18 '22 02:11

Yebach