rzclfy
rzclfy

Reputation: 55

Upload directly to S3 from django

I'm really stuck here.

I want to be able to upload directly to S3 from a django form. This is going to be used to hold display pictures.

I followed this: http://django-storages.readthedocs.org/en/latest/backends/amazon-S3.html

but unfortunately I get stuck at adding

DEFAULT_FILE_STORAGE = 'storages.backends.s3.S3Storage'

to settings.py for some reason. django doesn't even recognize the change that i have made. (I changed it to DEFAULT_FILE_STORAGE = 'asdsfsdfsdf' and it didn't even give an error. The funny thing is that I don't even know if django-storages has the feature i'm looking for.

Upvotes: 3

Views: 6624

Answers (3)

Victor Trac
Victor Trac

Reputation: 391

This isn't too difficult. The steps are to generate a policy document, sign it, and then use that signature to POST the file to S3. I wrote a little application called sbit3 that does this. Take a look here: https://github.com/victortrac/sbit3/blob/master/server/sbit3.py, specifically the PostHandler class:

class PostHandler(tornado.web.RequestHandler):
    def _generate_policy_doc(self, conditions, expiration=None):
        if not expiration:
            # Sets a policy of 15 minutes to upload file
            expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=15)
        conditions = [ { "bucket" : conditions["bucket"] },
                       [ "starts-with", "$key", "uploads/"],
                       { "acl" : conditions["acl"] },
                       { "success_action_redirect" : conditions["success_action_redirect"] } ]
        conditions_json = json.dumps({ "expiration" : expiration.strftime("%Y-%m-%dT%H:%M:%SZ"),
                                       "conditions" : conditions })
        logging.debug("Policy doc generated: {0}".format(conditions_json))
        return base64.b64encode(conditions_json)

    def _sign_policy(self, policy):
        signature = base64.b64encode(hmac.new(settings.aws_secret_key, policy, hashlib.sha1).digest())
        return signature

    def get(self, expiration):
        try:
            expiration = int(expiration)
            # Set max expiration to 7200 minutes (5 days)
            if not 0 < expiration < 7200:
                raise tornado.web.HTTPError(403)
            _expireTimestamp = datetime.datetime.utcnow() + datetime.timedelta(minutes=expiration)
        except ValueError:
            raise tornado.web.HTTPError(403)

        # Associate _uuid to expiration in sdb

        _uuid = uuid.uuid4().hex
        sdb_conn.add_item(_uuid, expireTimestamp=_expireTimestamp)

        conditions = { "bucket" : settings.bucket,
                       "acl" : settings.acl,
                       "success_action_redirect" : settings.site_url + "/f/" + _uuid }
        policy_document = self._generate_policy_doc(conditions)
        signature = self._sign_policy(policy_document)

        self.render("post.html", conditions=conditions,
                                 aws_access_id=settings.aws_access_id,
                                 policy_document=policy_document,
                                 signature=signature)

The look at the post.html that sets up the form:

<form action="https://{{ conditions["bucket"] }}.s3.amazonaws.com" method="post" enctype="multipart/form-data">
  <input type="hidden" name="key" value="uploads/${filename}">
  <input type="hidden" name="AWSAccessKeyId" value="{{ aws_access_id }}"> 
  <input type="hidden" name="acl" value="{{ conditions["acl"] }}"> 
  <input type="hidden" name="success_action_redirect" value="{{ conditions["success_action_redirect"] }}">
  <input type="hidden" name="policy" value="{{ policy_document }}">
  <input type="hidden" name="signature" value="{{ signature }}">

  File to upload to S3: 
  <input name="file" type="file"> 
  <br> 
  <input type="submit" value="Upload File to S3"> 
</form> 

Upvotes: 3

Yaniv Aknin
Yaniv Aknin

Reputation: 4253

Here's a working example I once wrote, django-s3upload.

Upvotes: 4

Kekoa
Kekoa

Reputation: 28230

In order to upload directly to S3(bypassing your webserver) you will need to directly post through the browser to a pre-authorized url. Read this article from amazon that explains how it needs to work.

I don't know of anything that will do this for you in django, but it is not too difficult to make the request yourself. You can also use something like uploadify to do the actual posting from the browser, you just need to give it the right url.

Upvotes: 2

Related Questions