Cloudflare Images for Django

By Justin

Cloudflare Images for Django
In this blog post tutorial, we are going to start using the Cloudflare Image service to speed up the delivery of our images.... but what does this mean exactly?
Let's say you create a blog post and you want to include an image in it. You upload the image to your server, and when someone visits your blog post, the image is served from your server. Sometimes this image can be huge like 5MB or more and rendered out as 5000px x 4000px or something massive. This can slow down your website significantly regardless of how it's being served.
One of the ways to solve this problem is to make multiple variations of your image:
  • 5000px x 4000px (original)
  • 1200px x 800px (large)
  • 600px x 400px (medium)
  • 300px x 200px (small)
  • 100px x 100px (tiny)
In order to do this, you need to build out an image pipeline for all of your images.
Instead of building out an image pipeline ourselves, we can leverage Cloudflare Images -- which does this exact thing. But that's not the best part. The best part how you can use these image variants in any of your websites:
  • https://imagedelivery.net/{account_hash}/{image_id}/public (original)
  • https://imagedelivery.net/{account_hash}/{image_id}/large (large)
  • https://imagedelivery.net/{account_hash}/{image_id}/medium (medium)
  • https://imagedelivery.net/{account_hash}/{image_id}/small (small)
  • https://imagedelivery.net/{account_hash}/{image_id}/tiny (tiny)
Notice how the base image is the same for all with a slight url change for each variant? That's the power of Cloudflare Images. This blog post is going to show you exactly how to use Cloudflare Images in your Python projects and more specifically in Django (because of the Django admin).
Let's get started!

Step 1: Set up the Django project

  • Open your terminal and navigate to your project directory
  • Create a virtual environment:
mkdir -p path/to/project
cd path/to/project
python3 -m venv venv
On Windows PowerShell, you need to use the absolute path to the Python executable (e.g. C:\Python312\python.exe -m venv venv).
  • Activate the virtual environment:
On macOS, Linux, and WSL:
source venv/bin/activate
Or on Windows PowerShell:
venv\Scripts\activate
  • Install Django and other required packages:
pip install django requests python-decouple
  • Create a new Django project:
mkdir -p src
cd src
django-admin startproject cfehome .
  • Create two new Django apps:
python manage.py startapp images
python manage.py startapp blog
  • Open cfehome/settings.py and add both apps to INSTALLED_APPS:
python
INSTALLED_APPS = [
    # ...
    'images',
    'blog',
]

Step 2: Create the Image and BlogPost models

  • Open images/models.py and add the following code:
python
from django.db import models

class Image(models.Model):
    title = models.CharField(max_length=200)
    cloudflare_id = models.CharField(max_length=200)
    uploaded_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title
  • Open blog/models.py and add the following code:
python
from django.db import models
from django.conf import settings
from images.models import Image

class BlogPost(models.Model):
    title = models.CharField(max_length=120)
    content = models.TextField(blank=True, null=True)
    timestamp = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    image = models.ForeignKey(Image, null=True, blank=True, on_delete=models.SET_NULL)
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

    def __str__(self):
        return self.title
  • Create and apply migrations:
python manage.py makemigrations
python manage.py migrate

Step 3: Set up Cloudflare Images and Dotenv

Create a .env file in the root of your project and add the following:
CLOUDFLARE_ACCOUNT_ID=your_account_id
CLOUDFLARE_API_KEY=your_api_key
CLOUDFLARE_ACCOUNT_HASH=your_account_hash
CLOUDFLARE_IMAGES_DOMAIN=imagedelivery.net
CLOUDFLARE_EMAIL=your_cloudflare_email
  1. Sign up for Cloudflare: If you don't have a Cloudflare account, go to https://dash.cloudflare.com/sign-up and create one.
  2. Access Cloudflare Images:
    • Log in to your Cloudflare dashboard.
    • In the left sidebar, click on "Images".
    • If prompted, click "Add Storage" to enable Cloudflare Images for your account.
  3. Get your Account ID and Account Hash:
    • Still in Cloudflare Images, copy the Account ID (e.g. 12abc123123abc123123abc123)
    • In .env, replace the CLOUDFLARE_ACCOUNT_ID placeholder with your Account ID.
    • Under Account ID, you can see your Account Hash (e.g. ~dbasd1234)
    • In .env replace the CLOUDFLARE_ACCOUNT_HASH placeholder with your Account Hash.
  4. Create an API key:
    • In the Cloudflare dashboard, go to "My Profile" (click your email in the top right).
    • Navigate to the "API Tokens" tab.
    • Click "Create Token".
    • Under "Custom Token" click "Get started".
    • Give your token a name (e.g., "CFE Django Images Key").
    • Under "Permissions", add the following:
      • Account - Cloudflare Images - Edit
    • Set the "Account Resources" to include your account.
    • Click "Continue to summary" and then "Create Token".
    • Copy the generated key immediately (you won't be able to see it again).
    • In .env, replace the CLOUDFLARE_API_KEY placeholder with your API key.

Step 4: Add Cloudflare Credentials in Django Settings

Update cfehome/settings.py to load these environment variables:
python
from decouple import config

# ...

CLOUDFLARE_ACCOUNT_ID = config('CLOUDFLARE_ACCOUNT_ID')
CLOUDFLARE_API_KEY = config('CLOUDFLARE_API_KEY')
CLOUDFLARE_ACCOUNT_HASH = config('CLOUDFLARE_ACCOUNT_HASH')
CLOUDFLARE_IMAGES_DOMAIN = config('CLOUDFLARE_IMAGES_DOMAIN', default='imagedelivery.net')
CLOUDFLARE_EMAIL = config('CLOUDFLARE_EMAIL')

Step 5: Create services for Image upload

Create a new file images/services.py with the following content:
python
import requests
from django.conf import settings


def upload_image_to_cloudflare(image_file):
    url = f"https://api.cloudflare.com/client/v4/accounts/{settings.CLOUDFLARE_ACCOUNT_ID}/images/v1"
    headers = {"Authorization": f"Bearer {settings.CLOUDFLARE_API_KEY}"}

    with image_file.open("rb") as file:
        files = {"file": (image_file.name, file)}
        response = requests.post(url, headers=headers, files=files)

    response.raise_for_status()
    return response.json()["result"]["id"]


def get_image_url_from_cloudflare(image_id, variant="public"):
    url = settings.CLOUDFLARE_IMAGES_DOMAIN
    account_hash = settings.CLOUDFLARE_ACCOUNT_HASH
    return f"https://{url}/{account_hash}/{image_id}/{variant}"

Step 6: Create forms for Image upload and Blog Post Form

Create a new file images/forms.py with the following content:
python
from django import forms

from .models import Image
from .services import upload_image_to_cloudflare


class ImageUploadForm(forms.ModelForm):
    image_file = forms.ImageField(required=False)

    class Meta:
        model = Image
        fields = ["title"]

    def save(self, commit=True):
        instance = super().save(commit=False)
        image_file = self.cleaned_data.get("image_file")
        if image_file:
            instance.cloudflare_id = upload_image_to_cloudflare(image_file)

        if commit:
            instance.save()
        return instance

Create a new file blog/forms.py with the following:
python
from django import forms

from images.models import Image
from images.services import upload_image_to_cloudflare

from .models import BlogPost


class BlogPostForm(forms.ModelForm):
    image_file = forms.ImageField(required=False)

    class Meta:
        model = BlogPost
        fields = ["title", "content", "author"]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.instance.pk and self.instance.image:
            self.fields["image_file"].initial = self.instance.image

    def save(self, commit=True):
        instance = super().save(commit=False)
        image_file = self.cleaned_data.get("image_file")

        if image_file:
            cloudflare_id = upload_image_to_cloudflare(image_file)
            image, created = Image.objects.get_or_create(
                title=self.cleaned_data["title"],
                defaults={"cloudflare_id": cloudflare_id},
            )
            instance.image = image
        elif not self.cleaned_data.get("image_file") and instance.image:
            instance.image = None

        if commit:
            instance.save()
        return instance

Step 7: Configure Django Admin for BlogPost with Image Upload

Update images/admin.py with the following code:
python
from django.contrib import admin
from .models import Image
from .forms import ImageUploadForm

@admin.register(Image)
class ImageAdmin(admin.ModelAdmin):
    form = ImageUploadForm
Update blog/admin.py with the following code:
python
from django.contrib import admin
from django.utils.html import format_html

from images import services as image_services

from .forms import BlogPostForm
from .models import BlogPost


@admin.register(BlogPost)
class BlogPostAdmin(admin.ModelAdmin):
    form = BlogPostForm
    fields = ("title", "content", "image_file", "author", "display_image")
    readonly_fields = ("display_image",)
    list_display = ("title", "author", "created_at", "updated_at")

    def display_image(self, obj):
        if obj.image:
            img_url = image_services.get_image_url_from_cloudflare(
                obj.image.cloudflare_id, variant="thumbnailSmall"
            )
            img_html = f'<img src="{img_url}"  />'
            return format_html(img_html)
        return "No image"

    display_image.short_description = "Current Image"

Step 8: Verify up URLs for admin

Update cfehome/urls.py:
python
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]

Step 9: Create a superuser for admin access

Run the following command and follow the prompts to create a superuser:
bash
python manage.py createsuperuser

Step 10: Run the development server

  • Start the Django development server:
python manage.py runserver
  • Open your browser and go to http://127.0.0.1:8000/admin/ to access the Django admin interface.
  • Log in with the superuser credentials you created.
  • You should now be able to create, edit, and delete BlogPost entries, as well as upload and associate images with them through the admin interface.
This completes the setup of your Django project with Cloudflare Images integration and a BlogPost admin interface. The project structure includes separate files for services and forms, improving modularity and maintainability.
Discover Posts
Cloudflare Images for Django