Use a Google Cloud Storage Backend with Your Django Project

As demonstrated in "Set Up and Deploy Your First Django Project", DjangoWhiteNoise is a great solution to serving static assets for Django sites on Eldarion Cloud.

The Python buildpack provided by Eldarion Cloud executes manage.py collectstatic during each deploy, so the web service always has the most recent copy of static assets.

A different approach is required for user-uploaded media or other static assets that are not included in the project repository when deploying the site. If the default django.core.files.storage.FileSystemStorage backend is used to manage media assets, media uploaded to the web service will be inconsistent across multiple running replicas, and would be lost each time a web service is deployed.

We recommend using Google Cloud Storage to host and serve media assets, and this how-to provides step-by-step instructions on configuring django-gapc-storage to provide a Google Cloud Storage backend to your project.

Setting up a project, service account and bucket in Google Cloud Platform

Project

We recommend creating a Google Cloud Platform project that maps to the site's resource on Eldarion Cloud.

If Eldarion is billing the resource group, it is best to create a new project using the name of the resource group attached to Eldarion's GCP billing account. Ask a cluster admin for assistance in creating a new project and getting added to the project as an owner.

Service Account

For each site within a project, we will create a service account that can be used to authenticate against and perform write and destroy options against a bucket set up for the site.

Bucket

Once the service account has been created, we will set up a new bucket to store the site's media and enable the proper permissions for our service account and anonymous users accessing the bucket.

Install and configure a custom storage backend using django-gapc-storage

import base64
import json
import os

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured

from gapc_storage.storage import GoogleCloudStorage
from oauth2client.client import SERVICE_ACCOUNT
from oauth2client.service_account import ServiceAccountCredentials


class ECGoogleCloudStorage(GoogleCloudStorage):
    """
    Custom subclass of GoogleCloudStorage to interact with Eldarion Cloud

    To create:

        ec instances env GCS_CREDENTIALS=$(cat key.json | base64) GCS_BUCKET=<bucket>

    """

    path_prefix = settings.MEDIA_PREFIX

    def get_oauth_credentials(self):
        client_credentials = json.loads(base64.b64decode(os.environ["GCS_CREDENTIALS"]))
        if client_credentials["type"] == SERVICE_ACCOUNT:
            creds = ServiceAccountCredentials.from_json_keyfile_dict(client_credentials)
        else:
            raise ImproperlyConfigured("non-service accounts are not supported")
        return self.create_scoped(creds)
...
DEFAULT_FILE_STORAGE = "<yoursite>.storage.ECGoogleCloudStorage"

GCS_ROOT = "https://storage.googleapis.com/{bucket_name}/".format(
    bucket_name=os.environ.get("GCS_BUCKET")
)

MEDIA_PREFIX = "media"
MEDIA_URL = "{gcs_root}{prefix}/".format(
    gcs_root=GCS_ROOT,
    prefix=MEDIA_PREFIX,
)