Appearance
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 responsepython
# 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=123Best Practices
- Use Celery for async generation
- Store images locally using Django's storage
- Use signals for automatic generation
- Create management commands for batch operations
- Cache responses in views