Login

Amazon S3 browser-based upload form(FIXED)

Author:
grillermo
Posted:
October 4, 2012
Language:
Python
Version:
1.3
Score:
0 (after 0 ratings)

This is a 'fixed' version of snippet 1868 Changes: Correctly handle the Content-Type, because amazon requieres it to be named with a dash and we can't use dashes in the form attributes declaration. Also added max_size handling, with the corresponding update to the policy generation. *Added an example usage with some javascript for basic validation.

See the amazon reference

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
## On your app.forms.py

from member.models import UserProfile
from django import forms
import datetime

import base64
import hmac, sha, simplejson


class S3UploadForm(forms.Form):
    """
    http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1434

    <input type="hidden" name="key" value="uploads/${filename}">
    <input type="hidden" name="AWSAccessKeyId" value="YOUR_AWS_ACCESS_KEY"> 
    <input type="hidden" name="acl" value="private"> 
    <input type="hidden" name="success_action_redirect" value="http://localhost/">
    <input type="hidden" name="policy" value="YOUR_POLICY_DOCUMENT_BASE64_ENCODED">
    <input type="hidden" name="signature" value="YOUR_CALCULATED_SIGNATURE">
    <input type="hidden" name="content_type" value="image/jpeg">
    """
    key = forms.CharField(widget = forms.HiddenInput)
    AWSAccessKeyId = forms.CharField(widget = forms.HiddenInput)
    acl = forms.CharField(widget = forms.HiddenInput)
    success_action_redirect = forms.CharField(widget = forms.HiddenInput)
    policy = forms.CharField(widget = forms.HiddenInput)
    signature = forms.CharField(widget = forms.HiddenInput)
    content_type = forms.CharField(widget = forms.HiddenInput)
    file = forms.FileField()
    
    def __init__(self, aws_access_key, aws_secret_key, bucket, key,
            expires_after = datetime.timedelta(days = 30),
            acl = 'public-read',
            success_action_redirect = None,
            content_type = '',
            min_size = 0,
            max_size = None,
        ):
        self.aws_access_key = aws_access_key
        self.aws_secret_key = aws_secret_key
        self.bucket = bucket
        self.key = key
        self.expires_after = expires_after
        self.acl = acl
        self.success_action_redirect = success_action_redirect
        self.content_type = content_type
        self.min_size = min_size
        self.max_size = max_size
        
        policy = base64.b64encode(self.calculate_policy())
        signature = self.sign_policy(policy)
        
        initial = {
            'key': self.key,
            'AWSAccessKeyId': self.aws_access_key,            
            'acl': self.acl,
            'policy': policy,
            'signature': signature,
            'content_type': self.content_type,
        }
        if self.success_action_redirect:
            initial['success_action_redirect'] = self.success_action_redirect
        
        super(S3UploadForm, self).__init__(initial = initial)
        
        if self.content_type:
            # we change the name of the field to the S3 required
            self.fields['content_type'].widget.attrs.update({'name':'Content-Type'})
        # We need to manually rename the content_type field to content_type
        if self.max_size:
            self.fields['MAX_SIZE'] = forms.CharField(widget=forms.HiddenInput)
            self.initial['MAX_SIZE'] = self.max_size
        
        # Don't show success_action_redirect if it's not being used
        if not self.success_action_redirect:
            del self.fields['success_action_redirect']

    def add_prefix(self, field_name):
        # Hack to use the S3 required field name
        if field_name == 'content_type' and self.content_type:
            field_name = 'Content-Type'
        return super(S3UploadForm, self).add_prefix(field_name)

    def as_html(self):
        """
        Use this instead of as_table etc, because S3 requires the file field
        come AFTER the hidden fields, but Django's normal form display methods
        position the visible fields BEFORE the hidden fields.
        """
        html = ''.join(map(unicode, self.hidden_fields()))
        html += unicode(self['file'])
        return html
    
    def as_form_html(self, prefix='', suffix=''):
        html = """
        <form action="%s" method="post" enctype="multipart/form-data">
        <p>%s <input type="submit" value="Upload"></p>
        </form>
        """.strip() % (self.action(), self.as_html())
        return html
    
    def is_multipart(self):
        return True
    
    def action(self):
        return 'https://%s.s3.amazonaws.com/' % self.bucket
    
    def calculate_policy(self):
        conditions = [
            {'bucket': self.bucket},
            {'acl': self.acl},
            ['starts-with', '$key', self.key.replace('${filename}', '')],
            ["starts-with", "$Content-Type", self.content_type],
        ]
        if self.content_type:
            conditions.append(
                ['starts-with','$Content-Type',self.content_type]
            )
        if self.max_size:
            conditions.append(
                ['content-length-range',self.min_size,self.max_size]
            )
        if self.success_action_redirect:
            conditions.append(
                {'success_action_redirect': self.success_action_redirect},
            )
        
        policy_document = {
            "expiration": (
                datetime.datetime.now() + self.expires_after
            ).isoformat().split('.')[0] + 'Z',
            "conditions": conditions,
        }
        return simplejson.dumps(policy_document, indent=2)
    
    def sign_policy(self, policy):
        return base64.b64encode(
            hmac.new(self.aws_secret_key, policy, sha).digest()
        )

## On a app.view.py

def profile_image(request,template_name=None,user=None):
    s3uploadform = S3UploadForm(
        settings.AWS_ACCESS_KEY_ID,
        settings.AWS_SECRET_ACCESS_KEY,
        settings.AWS_STORAGE_BUCKET_NAME,
        'uploads/'+user+'_photo.jpg',
        content_type='image/',
        success_action_redirect = 'http://domain/johnTheUser/upload_success',
        max_size=734003, # 700 kbs
        expires_after = datetime.timedelta(days=99999) #forever!
        )
    context = {'form':s3uploadform}
    return render_to_response(template_name,context,
                    context_instance=RequestContext(request))


## On a template.html

<html>
    <head>
        <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
        <script>
            $(function(){
                var max_size = {{form.max_size}};
                var allowed_file_extensions = ['png','jpg'];
                var $upload_form = $('#image_upload_form');
                $('#id_file').change(function(){
                    if($(this).val()){
                        file_name = $(this).val();
                        file_extension = file_name.substring(file_name.length - 3,file_name.length)
                        if($.inArray(file_extension,allowed_file_extensions) < 0){
                            alert('Wrong file type');
                            return false;
                        }
                        if($(this)[0].files[0].size > max_size){
                            alert('The file is too big.')
                            return false;
                        }
                        $upload_form.submit();
                    }
                    else{
                        return false;
                    }
                })
            })
        </script>
    </head>
    <form id='upload_form' action="https://{{BUCKET_NAME}}.amazonaws.com/" method="post" enctype="multipart/form-data">
    {{form.key}}
    {{form.AWSAccessKeyId}}
    {{form.acl}}
    {{form.success_action_redirect}}
    {{form.policy}}
    {{form.signature}}
    {{form.content_type}}
    {{form.file}}
    <input type="submit" value="Save" />
    </form>
</html>

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 11 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 2 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 7 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

Please login first before commenting.