Django-s3direct 上传图片
我想用django-s3direct这个工具,在管理面板里上传很多图片。
1) 每次我尝试上传图片或文件时,都会出现错误提示“哎呀,文件上传失败,请再试一次”。当我刷新页面时,文件名会出现在输入框里,但我的“保存”按钮是灰色的,不能点击 :/
补充说明 我从设置里删除了一些东西:
AWS_SECRET_ACCESS_KEY = ''
AWS_ACCESS_KEY_ID = ''
AWS_STORAGE_BUCKET_NAME = ''
现在我没有再收到错误提示,但文件还是上传不上去 :/ 进度条一直是黑色的,没反应……
2) 怎么上传多个图片?不想用内联的方式……请帮帮我,给点建议?我还是个新手……
我现在用的是Django 1.5.5。现在我在用内联的功能,但我不知道接下来该怎么做。
1 个回答
你需要修改目标 S3 存储桶的一些权限设置,以确保最终的请求有足够的权限可以写入这个存储桶。首先,登录到 AWS 控制台,选择 S3 部分。找到合适的存储桶,然后点击“属性”标签。接着选择“权限”部分,这里会提供三个选项(添加更多权限、编辑存储桶策略和编辑 CORS 配置)。
CORS(跨源资源共享)允许你的应用访问 S3 存储桶中的内容。每条规则应该指定一组可以访问存储桶的域名,以及允许这些域名使用的方法和头信息。
为了让你的应用正常工作,点击“添加 CORS 配置”,然后输入以下 XML:
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>yourdomain.com</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
在 CORS 窗口中点击“保存”,然后在存储桶的“属性”标签中再次点击“保存”。这告诉 S3 允许任何域名访问存储桶,并且请求可以包含任何头信息。为了安全起见,你可以将“AllowedOrigin”更改为只接受来自你自己域名的请求。
如果你希望为这个应用专门使用 S3 凭证,可以在 AWS 账户页面生成更多密钥。这提供了更高的安全性,因为你可以指定这组密钥能够执行的非常具体的请求。如果你更倾向于这样做,那么你还需要在 S3 存储桶的编辑存储桶策略选项中设置一个 IAM 用户。AWS 的网页上有各种指南详细说明如何完成这一步。
设置客户端代码
这个设置不需要额外的、非标准的 Python 库,但需要一些脚本来完成客户端的实现。本文介绍了 s3upload.js 脚本。你可以从项目的仓库中获取这个脚本(使用 Git 或其他方式),并将其存放在应用的静态目录中的合适位置。这个脚本目前依赖于 JQuery 和 Lo-Dash 库。关于如何在你的应用中包含这些库,后面会详细说明。
现在可以创建 HTML 和 JavaScript 来处理文件选择,从你的 Python 应用获取请求和签名,然后最终发起上传请求。
首先,在应用的模板目录中创建一个名为 account.html 的文件,并根据你的应用适当填充头部和其他必要的 HTML 标签。在这个 HTML 文件的主体部分,包含一个文件输入和一个用于显示上传进度状态更新的元素。
<input type="file" id="file" onchange="s3_upload();"/>
<p id="status">Please select a file</p>
<div id="preview"><img src="/static/default.png" /></div>
<form method="POST" action="/submit_form/">
<input type="hidden" id="" name="" value="/static/default.png" />
<input type="text" name="example" placeholder="" /><br />
<input type="text" name="example2" placeholder="" /><br /><br />
<input type="submit" value="" />
</form>
预览元素最初会显示一张默认图片。当用户选择新图片时,JavaScript 会更新这两个元素。因此,当用户最终点击提交按钮时,图片的 URL 和用户的其他信息会一起提交到你想要的服务器端处理的端点。用户选择文件时,会调用 JavaScript 方法 s3_upload()。这个方法的创建和填充将在下面讨论。
接下来,在 HTML 文件 account.html 中包含这三个依赖脚本。如果你将 s3upload.js 文件放在了 /static 目录以外的地方,可能需要调整 src 属性:
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script type="text/javascript" src="https://raw.github.com/bestiejs/lodash/v1.1.1/dist/lodash.min.js"></script>
<script type="text/javascript" src="/static/s3upload.js"></script>
脚本的顺序很重要,因为依赖关系需要按这个顺序满足。如果你希望托管自己版本的 JQuery 和 Lo-Dash,请相应地调整 src 属性。
最后,在同一个文件中声明一个 JavaScript 函数 s3_upload(),用于处理文件上传。这个块需要放在包含三个依赖项的下面:
function s3_upload(){
var s3upload = new S3Upload({
file_dom_selector: 'file',
s3_sign_put_url: '/sign_s3_upload/',
onProgress: function(percent, message) {
$('#status').html('Upload progress: ' + percent + '%' + message);
},
onFinishS3Put: function(url) {
$('#status').html('Upload completed. Uploaded to: '+ url);
$("#image_url").val(url);
$("#preview").html('<img src="'+url+'" style="width:300px;" />');
},
onError: function(status) {
$('#status').html('Upload error: ' + status);
}
});
}
这个函数创建一个 S3Upload 的新实例,并传入文件输入元素、获取签名请求的 URL 和三个函数。
最初,函数会向 s3_sign_put_url 参数指定的 URL 发起请求,传递文件名和 MIME 类型作为 GET 参数。服务器端代码(在下一部分中讨论)会解析请求,并返回要上传到 S3 的文件的预览 URL 和签名请求,函数随后会使用这个请求异步上传文件到你的存储桶。
函数会将上传进度更新发送到 onProgress() 函数,如果上传成功,会调用 onFinishS3Put(),并将 Python 应用视图返回的 URL 作为参数接收。如果上传因任何原因失败,onError() 会被调用,status 参数会描述错误。
如果你发现页面在实现系统后没有按预期工作,可以考虑使用 console.log() 记录在 onError() 回调中发生的任何错误,并使用浏览器的错误控制台帮助诊断问题。
如果成功,预览 div 将更新为用户选择的图片,隐藏的输入字段将包含图片的 URL。现在,一旦用户完成表单的其余部分并点击提交,所有信息都可以发送到同一个端点。
在任何形式的应用(无论是网页还是设备应用)中,告知用户任何长时间的活动并显示状态更新是个好习惯。因此,状态方法可以用来显示一个加载 GIF,以指示上传正在进行中,上传完成后可以隐藏这个 GIF。如果没有这种信息,用户可能会怀疑页面崩溃,可能会尝试刷新页面或以其他方式干扰上传过程。
设置服务器端 Python 代码
为了生成一个临时签名,以便上传请求可以被签名。这个临时签名使用账户信息(AWS 访问密钥和秘密访问密钥)作为签名的基础,但用户不会直接访问这些信息。签名过期后,使用相同签名的上传请求将不会成功。
如前所述,本文介绍的是为 Flask 框架生成应用的过程,尽管其他 Python 框架的步骤会类似。使用 Python 3 的读者在继续之前应查看 Flask 网站上的相关信息。
首先创建你的主应用文件 application.py,并适当地设置你的骨架应用:
from flask import Flask, render_template, request
from hashlib import sha1
import time, os, json, base64, hmac, urllib
app = Flask(__name__)
if __name__ == '__main__':
port = int(os.environ.get('PORT', 5000))
app.run(host='0.0.0.0', port=port)
当前未使用的导入语句在后面会用到。
使用 Python 3 的读者应使用 urllib.parse 替代 urllib。
接下来,在同一个文件中,你需要创建负责在请求不同 URL 时返回正确信息的视图。首先定义一个视图,用于请求 /account,返回包含用户填写表单的页面 account.html:
@app.route("/account/")
def account():
return render_template('account.html')
请注意,应用的视图需要放在 app = Flask(__name__) 和 if __name__ == '__main__':
这两行之间。
现在在同一个 Python 文件中创建一个视图,负责生成并返回签名,以便客户端 JavaScript 可以上传图片。这是客户端在尝试上传到 S3 之前发出的第一个请求。这个视图响应请求到 /sign_s3/:
@app.route('/sign_s3/')
def sign_s3():
AWS_ACCESS_KEY = os.environ.get('AWS_ACCESS_KEY_ID')
AWS_SECRET_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
S3_BUCKET = os.environ.get('S3_BUCKET')
object_name = request.args.get('s3_object_name')
mime_type = request.args.get('s3_object_type')
expires = int(time.time()+10)
amz_headers = "x-amz-acl:public-read"
put_request = "PUT\n\n%s\n%d\n%s\n/%s/%s" % (mime_type, expires, amz_headers, S3_BUCKET, object_name)
signature = base64.encodestring(hmac.new(AWS_SECRET_KEY, put_request, sha1).digest())
signature = urllib.quote_plus(signature.strip())
url = 'https://%s.s3.amazonaws.com/%s' % (S3_BUCKET, object_name)
return json.dumps({
'signed_request': '%s?AWSAccessKeyId=%s&Expires=%d&Signature=%s' % (url, AWS_ACCESS_KEY, expires, signature),
'url': url
})
使用 Python 3 的读者应使用 urllib.parse.quote_plus() 来引用签名。
这段代码执行以下步骤:
- 接收到对 /sign_s3/ 的请求,并从环境中加载 AWS 密钥和 S3 存储桶名称。
- 从请求的 GET 参数中提取要上传对象的名称和 MIME 类型(这一阶段在其他框架中可能有所不同)。
- 设置签名的过期时间,形成签名的临时特性。如示例所示,最好使用相对于当前 UNIX 时间的函数。在这个例子中,签名将在 Python 执行那行代码后 10 秒过期。
- headers 行告诉 S3 授予什么访问权限。在这种情况下,对象将公开可下载。
- 现在可以根据对象信息、头信息和过期时间构建 PUT 请求。
- 生成签名,作为编译后的 AWS 秘密密钥和实际 PUT 请求的 SHA 哈希。
- 此外,去掉签名两端的空格,并使用 quote_plus 转义特殊字符,以便通过 HTTP 更安全地传输。
- 生成要上传对象的预期 URL,作为 S3 存储桶名称和对象名称的组合。
- 最后,可以将签名请求和预期 URL 以 JSON 格式返回给浏览器。
你可能希望为对象分配一个自定义名称,而不是使用文件本身的名称,这样可以防止在 S3 存储桶中意外覆盖。这个名称可以与用户账户的 ID 相关联。例如,如果不这样做,你应该提供某种方法来正确引用名称,以防存在空格或其他特殊字符。此外,这也是你可以在此阶段对上传的文件进行检查,以限制某些文件类型访问的时机。例如,可以实现一个简单的检查,只允许 .png 文件继续。
有时,S3 可能会对使用包含特殊字符的临时签名的请求返回 403(禁止)错误。因此,适当地引用签名是很重要的,如上所示。
最后,在 application.py
中创建一个视图,负责接收用户上传图片、填写表单并点击提交后的账户信息。由于这将是一个 POST 请求,因此也需要将其定义为“允许的访问方法”。这个方法将响应对 /submit_form/ URL 的请求:
@app.route("/submit_form/", methods=["POST"])
def submit_form():
example = request.form[""]
example2 = request.form[""]
image_url = request.form["image_url"]
update_account(example, example2, image_url)
return redirect(url_for('profile'))
在这个例子中,调用了一个 update_account() 函数,但本文没有涵盖这个方法的创建。在你的应用中,此时应该提供一些功能,以便应用能够将这些账户详细信息存储在某种数据库中,并正确地将信息与用户的其他账户详细信息关联起来。
此外,本文(或伴随代码)中没有定义个人资料页面的 URL。理想情况下,例如,在更新账户后,用户会被重定向回自己的个人资料,以便查看更新的信息。
欲了解更多信息,请访问 http://www.tivix.com/blog/easy-user-uploads-with-direct-s3-uploading/