web2py上传原始文件名

Yeb*_*ach 7 sql factory web2py

我想用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)
Run Code Online (Sandbox Code Playgroud)

我猜session.filename = request.vars.file是你设置文件名的地方.为什么我得到自动生成的文件名no_data.smth.23u8o8274823zu4i2.smth

谢谢

Ant*_*ony 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
Run Code Online (Sandbox Code Playgroud)

这会将上传文件的文件名重新分配给用户输入的值,然后web2py会将用户输入的文件名编码为新文件名.但请注意,web2py依赖于文件扩展名来在下载时适当地设置HTTP标头,因此您可能需要添加一些逻辑以获取原始文件扩展名,以防用户输入错误.


小智 6

如果只是重命名文件,这将破坏下载机制.此外,有时您可能希望使用与原始名称不同的名称保存文件.我们假设你有以下模型:

db.define_table("files",
Field("name", unique=True),
Field("file", "upload"))
Run Code Online (Sandbox Code Playgroud)

您需要使用自定义存储和检索功能扩展上载字段:

Field("file", "upload", custom_store=store_file, custom_retrieve=retrieve_file)
Run Code Online (Sandbox Code Playgroud)

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

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'))
Run Code Online (Sandbox Code Playgroud)

现在在控制器中,您需要在完成数据库插入/更新之前修改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
Run Code Online (Sandbox Code Playgroud)

您还需要定义一个函数来下载文件,因为build.alload中的构建将不起作用:

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)
Run Code Online (Sandbox Code Playgroud)

要连接点,您需要构建表单.在下面的例子中,我使用的是新的网格机制,它比旧的学校形式更好(但尚未在书中记录).

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

def index():
    grid = SQLFORM.grid(db.files, onvalidation=validate, upload=upload)
    return {"grid":grid}
Run Code Online (Sandbox Code Playgroud)

如果您不想要网格的所有功能,则等效的控制器代码为:

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}
Run Code Online (Sandbox Code Playgroud)