使用web2py上传文件并保留原始文件名

7

我想使用SQL.factory()上传文件。 我希望能保留原始文件名。 我的代码目前是:

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)

我猜测 session.filename = request.vars.file 是你设置文件名的地方。为什么我会得到自动生成的文件名 no_data.smth.23u8o8274823zu4i2.smth?
谢谢。
3个回答

6

如果你只是重命名文件,这样会破坏下载机制。此外,有时候你可能想将文件保存为不同于原始文件的名称。我们假设你有以下模型:

db.define_table("files",
Field("name", unique=True),
Field("file", "upload"))

您需要使用定制的存储和检索功能扩展上传字段:

Field("file", "upload", custom_store=store_file, custom_retrieve=retrieve_file)

这些功能只是从固定的上传目录中读取/写入文件:

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'))

现在在控制器中,在数据库插入/更新之前,您需要修改form.vars并设置文件名。如果您想保留上传文件的原始名称,则不需要此操作。

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

你还需要定义一个函数来下载文件,因为内置的response.download无法使用:
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)

想要连接这些关联点,你需要构建表单。在下面的示例中,我使用了新的网格机制,这种机制比老式表单更好(但尚未在书中记录)。

upload = lambda filename: URL("download", args=[filename])

def index():
    grid = SQLFORM.grid(db.files, onvalidation=validate, upload=upload)
    return {"grid":grid}

如果您不需要所有的网格布局功能,相应的控制器代码如下:

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}

嗨,我已经实现了上述代码,它运行良好,但是"autodelete=True"没有起作用。虽然该行已从数据库中删除...但物理文件仍然存在。 - May

6
首先,request.vars.file是一个Python cgi.FieldStorage对象,因此session.filename = request.vars.file会导致错误。实际的文件对象是request.vars.file.file,而request.vars.file.filename是上传文件的原始名称。
当您通过上传字段上传文件时,web2py会自动生成一个新名称,形式为“table_name.field_name.random_id.b16encoded_original_filename.extension”。这样做是为了防止目录遍历攻击并启用下载机制(需要知道表和字段名称)。在SQLFORM.factory的情况下,没有数据库表名,因此默认为表名为“no_table”。
您展示的代码实际上不应该生成文件名“no_data.smth.23u8o8274823zu4i2.smth”。该文件名意味着您已经明确告诉SQLFORM.factory使用表名“no_data”(通过其table_name参数),并且上传字段名为“smth”。 (上面的代码将生成以“no_table.file”开头的文件名)。
请注意,web2py会自动获取上传文件的原始名称,并将其编码(使用b16encode)到新文件名中(然后在使用内置下载机制时解码原始文件名)。原始文件名也可以在form.vars.file.filename中使用。因此,您不一定需要用户输入文件名。但是,如果您想要允许用户输入可能与实际文件名不同的文件名,然后使用用户输入的文件名,可以在表单创建之前添加以下内容:
if 'file' in request.vars and request.vars.file_name:
    request.vars.file.filename = request.vars.file_name

这将重新分配上传文件的文件名为用户输入的值,然后web2py将把用户输入的文件名编码到新文件名中。然而,请注意,web2py依赖于文件扩展名来设置适当的HTTP头以便下载,所以如果用户未输入文件扩展名,则您可能需要添加一些逻辑来获取原始的文件扩展名。


通过request.vars.name_of_file.filename,我可以获取原始文件名,但如何重命名上传的文件呢?我应该使用os.rename吗? 我正在上传不同的zip文件,所以它们需要被命名为name_of_file.zip。谢谢。 - Yebach
你也可以跳过 form.accepts 并自己处理文件保存。但是不要在用户上传的文件中这样做,因为你会面临目录遍历攻击的风险。 - Anthony
如何在web2py中设置无文件编码。问题是我希望上传的文件以原始文件名存储在一个文件夹中,因为我有另一个脚本来处理它,文件名对于文件处理很重要。 - Yebach
就像我之前所说的,不要使用form.accepts。文件对象将在request.vars.file.file中,只需使用常规的Python代码将其保存在您喜欢的任何位置和方式即可。 - Anthony

2

我做到了 :) 这是我的代码

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)

我知道这可能不是最好的解决方案,但既然它能够工作,我就喜欢它 :)

谢谢Anthony


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接