Jay Shamnani
Jay Shamnani

Reputation: 149

How to upload file in AWS S3 with django app?

I am facing a problem uploading the user profile picture. Now when I create a user in Django admin and upload a file from the admin dashboard it works correctly and no errors. It goes to my AWS S3 bucket as it should go, but this is obviously not feasible, I have been looking for the solution for 3 to 4 days but no success or any satisfactory results. I obviously won't be providing the dashboard access to the user. The database used is MongoDB, with a database engine as djongo.

Here is my settings.py

INSTALLED_APPS = [
    'profileupload',
    's3direct',
    'storages',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.humanize',
]
STATIC_URL = '/static/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
AWS_SECRET_ACCESS_KEY = 'MY_SPECIAL_KEY'
AWS_ACCESS_KEY_ID = 'MY_SPECIAL_KEY_NAME'
AWS_STORAGE_BUCKET_NAME = 'S3_BUCKET'
AWS_S3_FILE_OVERWRITE = False
AWS_DEFAULT_ACL = None
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'

My urls.py

from django.urls import path, include
from .views import signup_form
urlpatterns = [
    path('signup', signup_form, name='signup'),
]

My models.py

class profile(models.Model):
    profile_id = models.AutoField(primary_key=True,unique=True)
    profile_username = models.CharField(max_length=100,unique=True)
    profile_name = models.CharField(max_length=150)
    profile_email = models.EmailField(max_length=200)
    profile_create_time = models.DateField(auto_now_add=True)
    profile_dob = models.DateField()
    profile_password = models.CharField(max_length=50, null=True)
    profile_picture = models.ImageField(default='default.jpg', upload_to='profile_pics')

    def __str__(self):
        return str(self.profile_username)

My views.py

def signup_form(request):
    if request.method == 'POST':
        if request.POST.get('profile_username') and request.POST.get('profile_name') and request.POST.get('profile_email') and request.POST.get('profile_dob') and request.POST.get('profile_password') and request.POST.get('profile_picture'):
            pr = profile()
            pr.profile_username = request.POST.get('profile_username')
            pr.profile_name = request.POST.get('profile_name')
            pr.profile_email = request.POST.get('profile_email')
            pr.profile_password = request.POST.get('profile_password')
            pr.profile_dob = request.POST.get('profile_dob')
            pr.profile_picture = request.POST.get('profile_picture')
            try:
                pr.save()
                print('setProfile success')
                return redirect('index.html')
            except Exception as e:
                return render(request, 'signup.html')
            return render(request, 'signup.html') 
    else:
        return render(request, 'signup.html')

My Sign up Form 'signup.html'

{% extends 'index.html' %}
{% block content %}

<form method='POST'>
    {% csrf_token %}
    <div>
        <label>USERNAME</label>
        <input type="text" placeholder="" name="profile_username" required/>
    </div><br>
    <div>
        <label>NAME</label>
        <input type="text" placeholder="" name="profile_name" required/>
    </div><br>
    <div>
        <label>EMAIL</label>
        <input type="email" placeholder="" name="profile_email" required/>
    </div><br>
    <div>
        <label>Password</label>
        <input type="password" placeholder="" name="profile_password" required/>
    </div><br>
    <div>
        <label>DOB</label>
        <input type="date" placeholder="" name="profile_dob" required/>
    </div><br>
    <div>
        <label>Profile Picture</label>
        <input type="file" placeholder="" name="profile_picture" required/>
    </div><br>
    <button type="submit">submit</button>
</form>
<a href="/">Home</a>
{% endblock content %}

Also, I want to change the name of the uploaded file, the file which is uploaded by Django admin takes the file name as it is, but when I will expose this app to the public the file name must be proper to avoid overwritten or having the same file multiple times

Upvotes: 2

Views: 18560

Answers (2)

Niel Godfrey P. Ponciano
Niel Godfrey P. Ponciano

Reputation: 10699

As already mentioned, one way is by directly using the boto3 library.

Another way is by using the save functionality of django-storages (which also uses boto3 in the background).

If there is only 1 bucket:

settings.py

...

INSTALLED_APPS = [
    ...
    "storages",
    ...
]

...


DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'

AWS_ACCESS_KEY_ID = 'my-access-key-id'
AWS_SECRET_ACCESS_KEY = 'my-secret-access-key'
# Depending on the AWS account used, you might also need to declare AWS_SESSION_TOKEN as an environment variable

AWS_STORAGE_BUCKET_NAME = 'my-bucket'

...

views.py

from io import BytesIO

from django.core.files.storage import default_storage
from rest_framework.decorators import api_view
from rest_framework.response import Response


@api_view(('GET',))
def save_file(request):
    file_name = "toguro.txt"
    file_content = b"I have my full 100% power now!"
    file_content_io = BytesIO(file_content)

    default_storage.save(file_name, file_content_io)

    return Response({"message": "File successfully saved"})

If there are many buckets:

settings.py

...

INSTALLED_APPS = [
    ...
    "storages",
    ...
]

...

AWS_ACCESS_KEY_ID = 'my-access-key-id'
AWS_SECRET_ACCESS_KEY = 'my-secret-access-key'
# Depending on the AWS account used, you might also need to declare AWS_SESSION_TOKEN as an environment variable

...

views.py

from io import BytesIO

from rest_framework.decorators import api_view
from rest_framework.response import Response
from storages.backends.s3boto3 import S3Boto3Storage


# For a clear separation-of-concern, you should consider placing this code to its appropriate place
class MyStorage1(S3Boto3Storage):
    bucket_name = 'my-bucket-1'


class MyStorage2(S3Boto3Storage):
    bucket_name = 'my-bucket-2'


@api_view(('GET',))
def save_file(request):
    file_name_1 = "toguro.txt"
    file_name_2 = "sensui.txt"
    file_content_1 = b"I have my full 100% power now!"
    file_content_2 = b"I will release the S-Class demons!"
    file_content_io_1 = BytesIO(file_content_1)
    file_content_io_2 = BytesIO(file_content_2)

    storage1 = MyStorage1()
    storage2 = MyStorage2()
    storage1.save(file_name_1, file_content_io_1)
    storage2.save(file_name_2, file_content_io_2)

    return Response({"message": "File successfully saved"})

Upvotes: 6

Jay Shamnani
Jay Shamnani

Reputation: 149

Okay, so I got some notion of applying the wrong access keys and taking wrong inputs in views.py

First of all taking correct inputs in views.py

pr.profile_picture = request.POST.get('profile_picture')

instead of using the above, the following would help:

pr.profile_picture  = request.FILES["profile_picture"]

also, when I use the above with single inverted commas it won't work, so keep that in mind.

Uploading the file to S3

Now there will be some other ways to do this but changing the name of the file at the same time.

I made another file specially to handle images and changing the name of the file.

import boto3

session = boto3.Session(
    aws_access_key_id= 'secret sauce',
    aws_secret_access_key = 'secret sauce'
)

class image():
    def UploadImage(name,image):
        filename = name+'_picture.jpg'
        imagedata = image
        s3 = boto3.resource('s3')
        try:
            object = s3.Object('bucket sauce', filename)
            object.put(ACL='public-read',Body=imagedata,Key=filename)
            return True
        except Exception as e:
            return e

above method is called in views.py

ret = image.UploadImage(pr.profile_username,pr.profile_picture)

put it in the try block to avoid errors.

Upvotes: 3

Related Questions