Configuring s3 bucket to serve media and static contents in Django app
Tags: aws, django, webdev, backend
Published: 2023-07-06 14:28:37

In my few years as a Django web developer, I have come across several issues surrounding building and deployment of web applications. Notable among them is preparing the application for deployment. When it comes to that, there are lots of good practices to put in place. One very important one which I am going to talk about is setting Debug=False.

Some of the things you will notice are your fonts not responding (note if you're using web fonts), the comprehensive yellow background error messages won't be showing anymore (which is a good thing though), and importantly, your user uploaded media contents won't show up.
 
To fix this issue, there are two main ways to go about this.
1. Using an s3 bucket to serve the contents.

2. Using a dedicated web server. You can also use a dedicated web server to serve media files. This is a good option if you need to have more control over how media files are served.

However I'm only going to talk about one(1).

#### Requirements:
* AWS account
* Django project
* Install django-storages `pip install django-storages` and add `storages` to installed_apps.
* Install boto3 library `pip install boto3`

First of all, head over to your AWS console and create an IAM user group. Then create IAM user with S3FullAccess policy and add to the user group. When creating the user credentials, select 'local code' as the use case. Download the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in a safe place. With this S3FullAccess policy, the user is able to perform all actions related to the s3 bucket. You can also limit it further by giving access to few actions.

Next is to head over to s3 dashboard and create a bucket. Give it a unique name of any bucket name in the region. Also, don't forget to give it public access else you won't be able to access it.

Create a file in the same folder as the settings.py and add the following code to it
```python
from django.conf import settings
from storages.backends.s3boto3 import S3Boto3Storage


class StaticStorage(S3Boto3Storage):
    location = 'static'
    default_acl = 'public-read'


class MediaStorage(S3Boto3Storage):
    location = 'media'
    default_acl = 'public-read'
    file_overwrite = False
```
These will override the S3Boto3Storage method and create the static and media methods to serve the contents.

Add the following code snippet below to your settings file (should be closer to the static file handlers) and do the necessary changes.

#### Serving static and media content via s3. 
Configuration

```python
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'project/static'),
]

AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID"
AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY"
AWS_STORAGE_BUCKET_NAME = 'bucket-name'
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME

AWS_S3_OBJECT_PARAMETERS = {
    'CacheControl': 'max-age=86400',
}

AWS_STATIC_LOCATION = 'static/'
STATICFILES_STORAGE = 'project.storage_backend.StaticStorage'
STATIC_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, AWS_STATIC_LOCATION)

AWS_PUBLIC_MEDIA_LOCATION = 'media/'
DEFAULT_FILE_STORAGE = 'project.storage_backend.MediaStorage'
```
* Restart your django server and run `python manage.py collectstatic`. If you have a good internet, it should take under 20 seconds to copy your static file to the s3 bucket. If you don't see the folders in the bucket after running collectstaic, refresh app and both static and media folders should show in the bucket. If you have a form for user uploaded media, save an image and the folder will be created.

That's the end. Yes! It's that simple.
However, you could face an issue with the web font you use in the project. 
If you realize the font doesn't work on the site, inspect the browser console and you will notice an error about access denied due to CORS headers. 
##### Fix
Go back to the s3 bucket and add the code snippet below to the CORS configuration section in the permissions menu of the s3 bucket. Substitute the localhost with your host in the code and you are good to go.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wuo3klv3oj9sowuodamw.png)

#### AWS CORS for web fonts -->
```json
[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "DELETE"
        ],
        "AllowedOrigins": [
            "http://localhost:8000"
        ],
        "ExposeHeaders": []
    }
]
'''