【问题标题】:web2py upload with original filenameweb2py 使用原始文件名上传
【发布时间】:2011-11-04 10:58:11
【问题描述】:

我想用 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

谢谢

【问题讨论】:

    标签: sql factory web2py


    【解决方案1】:

    首先,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 谢谢
    • 您也可以跳过form.accepts 并自己处理文件保存。但是,不要对用户上传的文件执行此操作,因为您将面临目录遍历攻击。
    • 如何通过 web2py 设置无文件编码。问题是我希望上传的文件以原始文件名存储在一个文件夹中,因为我有另一个脚本然后处理它并且文件名对于文件处理很重要??
    • 如我所说,那就不要使用 form.accepts。文件对象将在 request.vars.file.file 中——只需使用常规 Python 代码将其保存在您喜欢的任何位置。
    【解决方案2】:

    如果您只是重命名文件,则会破坏下载机制。此外,有时您可能希望以与原始文件不同的名称保存文件。假设您有以下模型:

    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”不起作用。虽然该行已从数据库中删除......但物理文件仍然存在。
    【解决方案3】:

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

    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)
    

    我知道这可能不是最好的解决方案,但因为它有效,我喜欢 :)

    谢谢你,安东尼

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-07-21
      • 2016-04-27
      • 1970-01-01
      • 2012-10-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多