Skip to content

Django Integration

Generate OG images for Django applications.

Setup

1. Configuration

python
# settings.py
OG_IMAGE_API_KEY = os.environ.get('OG_IMAGE_API_KEY')
OG_IMAGE_API_URL = 'https://ogimageapi.io/api/generate'

2. Create Service

python
# services/og_image.py
import requests
from django.conf import settings
from django.core.files.base import ContentFile


class OGImageService:
    def __init__(self):
        self.api_key = settings.OG_IMAGE_API_KEY
        self.api_url = settings.OG_IMAGE_API_URL
    
    def generate(self, params: dict) -> bytes | None:
        """Generate an OG image and return binary data."""
        default_params = {
            'template': 'default',
            'theme': 'dark',
        }
        
        payload = {**default_params, **params}
        
        try:
            response = requests.post(
                self.api_url,
                json=payload,
                headers={
                    'Content-Type': 'application/json',
                    'X-API-Key': self.api_key,
                },
                timeout=30,
            )
            response.raise_for_status()
            return response.content
        except requests.RequestException as e:
            print(f'OG Image generation failed: {e}')
            return None
    
    def generate_for_model(self, instance, template: str = 'blog') -> str | None:
        """Generate and save OG image for a model instance."""
        params = self._get_params_from_model(instance, template)
        image_data = self.generate(params)
        
        if not image_data:
            return None
        
        # Save to model's ImageField
        filename = f'og_{instance._meta.model_name}_{instance.pk}.png'
        instance.og_image.save(filename, ContentFile(image_data))
        instance.save(update_fields=['og_image'])
        
        return instance.og_image.url
    
    def _get_params_from_model(self, instance, template: str) -> dict:
        if template == 'blog':
            return {
                'title': instance.title,
                'subtitle': getattr(instance, 'excerpt', '')[:200],
                'author_name': str(instance.author) if hasattr(instance, 'author') else None,
                'author_avatar_url': getattr(instance.author, 'avatar_url', None) if hasattr(instance, 'author') else None,
                'template': 'blog',
            }
        elif template == 'product':
            return {
                'title': instance.name,
                'product_image_url': instance.image.url if instance.image else None,
                'price': f'${instance.price:.2f}',
                'original_price': f'${instance.compare_price:.2f}' if hasattr(instance, 'compare_price') and instance.compare_price else None,
                'rating': float(instance.average_rating) if hasattr(instance, 'average_rating') else None,
                'template': 'product',
            }
        else:
            return {
                'title': str(instance),
                'template': 'default',
            }


og_image_service = OGImageService()

Model Integration

Add Field to Model

python
# models.py
from django.db import models


class Post(models.Model):
    title = models.CharField(max_length=200)
    excerpt = models.TextField(blank=True)
    content = models.TextField()
    author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    og_image = models.ImageField(upload_to='og-images/', blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    def save(self, *args, **kwargs):
        is_new = self.pk is None
        title_changed = False
        
        if not is_new:
            old = Post.objects.filter(pk=self.pk).first()
            if old and old.title != self.title:
                title_changed = True
        
        super().save(*args, **kwargs)
        
        # Generate OG image for new posts or when title changes
        if is_new or title_changed:
            from services.og_image import og_image_service
            og_image_service.generate_for_model(self, 'blog')

Using Signals

python
# signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Post
from services.og_image import og_image_service


@receiver(post_save, sender=Post)
def generate_og_image(sender, instance, created, **kwargs):
    if created or instance.tracker.has_changed('title'):
        # Use Celery for async generation
        generate_og_image_task.delay(instance.pk)


# tasks.py (Celery)
from celery import shared_task
from .models import Post
from services.og_image import og_image_service


@shared_task
def generate_og_image_task(post_id):
    post = Post.objects.get(pk=post_id)
    og_image_service.generate_for_model(post, 'blog')

Template Context Processor

python
# context_processors.py
from django.conf import settings


def og_defaults(request):
    return {
        'default_og_image': settings.STATIC_URL + 'images/default-og.png',
        'site_name': 'My Django Site',
    }
python
# settings.py
TEMPLATES = [
    {
        ...
        'OPTIONS': {
            'context_processors': [
                ...
                'yourapp.context_processors.og_defaults',
            ],
        },
    },
]

Template Tag

python
# templatetags/og_tags.py
from django import template
from django.utils.safestring import mark_safe

register = template.Library()


@register.simple_tag(takes_context=True)
def og_meta(context, title, description='', image=None, type='website'):
    request = context.get('request')
    current_url = request.build_absolute_uri() if request else ''
    
    og_image = image or context.get('default_og_image', '')
    
    html = f'''
    <title>{title}</title>
    <meta name="description" content="{description}">
    
    <meta property="og:title" content="{title}">
    <meta property="og:description" content="{description}">
    <meta property="og:image" content="{og_image}">
    <meta property="og:url" content="{current_url}">
    <meta property="og:type" content="{type}">
    
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:title" content="{title}">
    <meta name="twitter:description" content="{description}">
    <meta name="twitter:image" content="{og_image}">
    '''
    
    return mark_safe(html)

Usage in templates:

html
{% load og_tags %}
<!DOCTYPE html>
<html>
<head>
    {% og_meta title=post.title description=post.excerpt image=post.og_image.url type="article" %}
</head>
<body>
    ...
</body>
</html>

View for Dynamic OG Images

python
# views.py
from django.http import HttpResponse
from django.views import View
from services.og_image import og_image_service


class OGImageView(View):
    def get(self, request):
        title = request.GET.get('title', 'My Site')
        subtitle = request.GET.get('subtitle', '')
        template = request.GET.get('template', 'default')
        theme = request.GET.get('theme', 'dark')
        
        params = {
            'title': title[:200],
            'subtitle': subtitle[:300],
            'template': template,
            'theme': theme,
        }
        
        image_data = og_image_service.generate(params)
        
        if not image_data:
            return HttpResponse(status=500)
        
        response = HttpResponse(image_data, content_type='image/png')
        response['Cache-Control'] = 'public, max-age=86400'
        return response
python
# urls.py
from django.urls import path
from .views import OGImageView

urlpatterns = [
    path('api/og/', OGImageView.as_view(), name='og_image'),
]

Management Command

python
# management/commands/generate_og_images.py
from django.core.management.base import BaseCommand
from yourapp.models import Post
from services.og_image import og_image_service


class Command(BaseCommand):
    help = 'Generate OG images for all posts'
    
    def add_arguments(self, parser):
        parser.add_argument(
            '--model',
            type=str,
            default='post',
            help='Model to generate images for',
        )
        parser.add_argument(
            '--id',
            type=int,
            help='Specific model ID',
        )
    
    def handle(self, *args, **options):
        model_name = options['model']
        model_id = options.get('id')
        
        if model_name == 'post':
            queryset = Post.objects.all()
            if model_id:
                queryset = queryset.filter(pk=model_id)
            
            total = queryset.count()
            
            for i, post in enumerate(queryset, 1):
                self.stdout.write(f'Processing {i}/{total}: {post.title[:50]}...')
                og_image_service.generate_for_model(post, 'blog')
            
            self.stdout.write(self.style.SUCCESS(f'Generated {total} OG images'))

Usage:

bash
python manage.py generate_og_images
python manage.py generate_og_images --model=post --id=123

Best Practices

  1. Use Celery for async generation
  2. Store images locally using Django's storage
  3. Use signals for automatic generation
  4. Create management commands for batch operations
  5. Cache responses in views

Generate stunning Open Graph images programmatically.