Jaewoong Lee

I'm Programmer for KAIST.

Django Amazon S3 Uploader

28 Jul 2020 » python, django

Amazon S3 or Amazon Simple Storage Service is a service offered by Amazon Web Services that provides object storage through a web service interface. Amazon S3 uses the same scalable storage infrastructure that Amazon.com uses to run its global e-commerce network.

AWS S3 Configuration

Before you can begin using Boto 3 (for AWS S3), you should set up authentication credentials. in this case, AWS S3 Only for upload files or images purpose.

a. Using awscli (recommended)

$ sudo apt-get install -y awscli
$ aws configure set aws_access_key_id 
$ aws configure set aws_secret_access_key 
$ aws configure set region   # e.g: ap-southeast-1

b. Manual Configuration

$ mkdir ~/.aws && nano ~/.aws/credentials

and then fill:

[default]
aws_access_key_id = xxxx
aws_secret_access_key = xxxx

You may also want to set a default region of your server. The ap-southeast-1 depend with your server region. This can be done in the configuration file. By default, its location is at ~/.aws/config:

[default]
region=ap-southeast-1

This example below is one of way “how to integrate” the AmazonS3 with Django (with django-rest-framework).

$ pip install requests boto3

in your settings.py

AWS_S3_BUCKET_NAME = 'xxx-xxx-xxx'
MAX_IMAGE_UPLOAD_SIZE = 5242880

then, in your utils/uploader.py

import os
import boto3
import urllib3
import datetime
import requests
import mimetypes

from django.conf import settings
from django.core.files.base import ContentFile
from botocore.exceptions import ClientError


class AmazonS3(object):
    path_format = datetime.datetime.now().strftime('%Y/%m/%d')
    bucket_name = getattr(settings, 'AWS_S3_BUCKET_NAME')
    client = boto3.client('s3')

    def get_object_name(self, file_url):
        """
        function to get the file object name
        or file path url by `file_url`.

        :param `file_url` is string uploaded url.
                eg: 'https://s3.ap-southeast-1.amazonaws.com/assets.dev.doain/files/2019/08/13/no-image.svg'

        :return string object_name.
                eg: 'files/2019/07/22/logo.png'
        """
        return urllib3.util.parse_url(file_url).path[1:]

    def get_uploaded_url(self, object_name):
        """
        function to get the uploaded url.

        :param `object_name` is string object name.
                eg: 'files/2019/07/22/logo.png'

        :return string of uploaded file/images url.
                eg: 'https://s3.ap-southeast-1.amazonaws.com/assets.dev.doain/files/2019/08/13/no-image.svg'
        """
        location = self.client.get_bucket_location(Bucket=self.bucket_name)['LocationConstraint']
        return 'https://s3.%s.amazonaws.com/%s/%s' % (location, self.bucket_name, object_name)

    def upload_file(self, file_name, folder='files'):
        """
        function to upload the file or image into aws s3.

        :param `file_name` is file name original path source.
        :param `folder` is target folder in aws s3.

        :return `url` eg: 'https://s3.ap-southeast-1.amazonaws.com/assets.dev.doain/files/2019/08/13/no-image.svg'
        """
        content_type = mimetypes.guess_type(file_name)[0]
        object_name = os.path.join(folder, self.path_format, file_name.split('/')[-1])
        res = self.client.upload_file(file_name, self.bucket_name, object_name,
                                      ExtraArgs={'ACL': 'public-read',
                                                 'ContentDisposition': 'inline',
                                                 'ContentType': content_type})
        return self.get_uploaded_url(object_name)

    def upload_fileobj(self, file_obj, folder='files'):
        """
        function to upload the file object or images into aws s3.

        :param `field_obj` is file object, eg: request.FILES['image']
        :param `folder` is target folder in aws s3.

        :return `url` eg: 'https://s3.ap-southeast-1.amazonaws.com/assets.dev.doain/files/2019/08/13/no-image.svg'
        """
        object_name = os.path.join(folder, self.path_format, file_obj.name.split('/')[-1])
        file_data = ContentFile(file_obj.read())
        self.client.upload_fileobj(file_data, self.bucket_name, object_name,
                                   ExtraArgs={'ACL': 'public-read',
                                              'ContentDisposition': 'inline',
                                              'ContentType': file_obj.content_type})
        return self.get_uploaded_url(object_name)

and then in your views.py

from django.conf import settings
from django.utils.translation import ugettext_lazy as _

from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import AllowAny

from yourapp.utils.uploader import AmazonS3


class UploadImageView(APIView):
    """
    this "Upload Image" view used to handle image uploader,
    and save into AmazonS3 storage.
    """
    permission_classes = [AllowAny,]
    image_types = ['image/png', 'image/jpg', 'image/jpeg', 'image/pjpeg',
                   'image/gif', 'image/vnd.microsoft.icon', 'image/x-icon']
    max_size = getattr(settings, 'MAX_IMAGE_UPLOAD_SIZE', 5242880)
    s3 = AmazonS3()

    def post(self, request, *args, **kwargs):
        folder = request.POST.get('folder', 'images')

        if 'image' in request.FILES:
            image = request.FILES['image']
            if image.content_type not in self.image_types:
                response = {'status': status.HTTP_400_BAD_REQUEST,
                            'message': _('Bad image format!')}
                return Response(response, status=response.get('status'))

            if image.size > self.max_size:
                max_size = self.max_size / (1024 * 1024)
                response = {'status': status.HTTP_400_BAD_REQUEST,
                            'message': _('Maximum image size %(max_size)s MB.') % {'max_size': max_size}}
                return Response(response, status=response.get('status'))

            image_url = self.s3.upload_fileobj(image, folder=folder)
            if not image_url:
                response = {'status': status.HTTP_400_BAD_REQUEST,
                            'message': _('Failed to upload the image.')}
                return Response(response, status=response.get('status'))

            response = {'status': status.HTTP_200_OK,
                        'message': _('Image successfully uploaded.'),
                        'result': {
                            'image_url': image_url,
                            'image_name': image.name
                        }}
            return Response(response, status=response.get('status'))

        response = {'status': status.HTTP_400_BAD_REQUEST,
                    'message': _('Invalid "image" field.')}
        return Response(response, status=response.get('status'))