Skip to content

Instantly share code, notes, and snippets.

@RyanBalfanz
Last active July 26, 2022 16:07
Show Gist options
  • Save RyanBalfanz/f07d827a4818fda0db81 to your computer and use it in GitHub Desktop.
Save RyanBalfanz/f07d827a4818fda0db81 to your computer and use it in GitHub Desktop.
[Heroku] Direct to S3 File Uploads in Python
"""
Fix for some issues with the original code from Heroku:
https://devcenter.heroku.com/articles/s3-upload-python
This example is also designed for use with Django, not Flask as in the original.
"""
import base64
import hashlib
import hmac
import logging
import os
import time
import urllib
from hashlib import sha1
from django.http import HttpResponseRedirect
from django.http import JsonResponse
from django.shortcuts import render
from .forms import UploadForm
logger = logging.getLogger(__name__)
def upload(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = UploadForm(request.POST, request.FILES)
# check whether it's valid:
if form.is_valid():
return HttpResponseRedirect('/uploads/done/')
# if a GET (or any other method) we'll create a blank form
else:
form = UploadForm()
return render(request, 'uploads/upload_form.html', {'form': form})
def sign_s3(request):
"""
https://devcenter.heroku.com/articles/s3-upload-python
"""
AWS_ACCESS_KEY = os.environ.get('AWS_ACCESS_KEY')
AWS_SECRET_KEY = os.environ.get('AWS_SECRET_KEY')
S3_BUCKET = os.environ.get('S3_BUCKET')
object_name = urllib.parse.quote_plus(request.GET['file_name'])
mime_type = request.GET['file_type']
secondsPerDay = 24*60*60
expires = int(time.time()+secondsPerDay)
amz_headers = "x-amz-acl:public-read"
string_to_sign = "PUT\n\n%s\n%d\n%s\n/%s/%s" % (mime_type, expires, amz_headers, S3_BUCKET, object_name)
encodedSecretKey = AWS_SECRET_KEY.encode()
encodedString = string_to_sign.encode()
h = hmac.new(encodedSecretKey, encodedString, sha1)
hDigest = h.digest()
signature = base64.encodebytes(hDigest).strip()
signature = urllib.parse.quote_plus(signature)
url = 'https://%s.s3.amazonaws.com/%s' % (S3_BUCKET, object_name)
return JsonResponse({
'signed_request': '%s?AWSAccessKeyId=%s&Expires=%s&Signature=%s' % (url, AWS_ACCESS_KEY, expires, signature),
'url': url,
})
def submit_form():
username = request.form["username"]
full_name = request.form["full_name"]
avatar_url = request.form["avatar_url"]
update_account(username, full_name, avatar_url)
return redirect(url_for('profile'))
@arseni-mourzenko
Copy link

Note that you're expected to include import urllib.parse, otherwise you'll probably get a:

AttributeError: 'module' object has no attribute 'parse'

around the line 49 urllib.parse.quote_plus.

@robintan
Copy link

Instead of import import urllib.parse
Line 49 should be changed to object_name = urllib.quote_plus(request.GET['file_name']) instead.

@Marcusdbb
Copy link

I don’t understand how this code or suppose to be implemented, nonthing is connected with nonthing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment