Screenshot API with Django: Views, Celery Tasks, and Django REST Framework
Django is the most common Python web framework for building SaaS applications, admin tools, and content platforms. This guide covers screenshot API integration patterns that match Django's conventions — views, Celery tasks, DRF serializers, and the cache framework.
Prerequisites
A free API key from the screenshot API. Set SCREENSHOT_API_KEY in your environment (.env via python-decouple or django-environ).
# settings.py
import os
SCREENSHOT_API_KEY = os.environ.get('SCREENSHOT_API_KEY', '')
SCREENSHOT_API_BASE = 'https://hermesforge.dev'
Basic Django View
The simplest pattern: a view that proxies screenshot requests to the API, keeping your key server-side.
# views.py
import requests
from django.http import HttpResponse, JsonResponse
from django.views import View
from django.conf import settings
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
class ScreenshotView(View):
def get(self, request):
target_url = request.GET.get('url')
if not target_url:
return JsonResponse({'error': 'Missing url parameter'}, status=400)
params = {
'url': target_url,
'format': request.GET.get('format', 'webp'),
'width': request.GET.get('width', '1280'),
'height': request.GET.get('height', '800'),
}
response = requests.get(
f'{settings.SCREENSHOT_API_BASE}/api/screenshot',
params=params,
headers={'X-API-Key': settings.SCREENSHOT_API_KEY},
timeout=30,
)
if not response.ok:
return JsonResponse(
{'error': f'Screenshot failed: {response.status_code}'},
status=response.status_code,
)
return HttpResponse(
response.content,
content_type=response.headers.get('content-type', 'image/webp'),
headers={'Cache-Control': 'public, max-age=3600'},
)
Add to urls.py:
from django.urls import path
from .views import ScreenshotView
urlpatterns = [
path('api/screenshot/', ScreenshotView.as_view(), name='screenshot'),
]
Django Cache Framework Integration
Cache screenshot responses to avoid redundant API calls:
# views.py
import hashlib
import requests
from django.http import HttpResponse, JsonResponse
from django.core.cache import cache
from django.views import View
from django.conf import settings
class CachedScreenshotView(View):
CACHE_TTL = 3600 # 1 hour
def get(self, request):
target_url = request.GET.get('url')
if not target_url:
return JsonResponse({'error': 'Missing url parameter'}, status=400)
fmt = request.GET.get('format', 'webp')
width = request.GET.get('width', '1280')
height = request.GET.get('height', '800')
# Cache key based on parameters
cache_key = 'screenshot:' + hashlib.sha256(
f'{target_url}:{fmt}:{width}:{height}'.encode()
).hexdigest()[:16]
cached = cache.get(cache_key)
if cached:
return HttpResponse(
cached['data'],
content_type=cached['content_type'],
headers={'X-Cache': 'HIT'},
)
response = requests.get(
f'{settings.SCREENSHOT_API_BASE}/api/screenshot',
params={'url': target_url, 'format': fmt, 'width': width, 'height': height},
headers={'X-API-Key': settings.SCREENSHOT_API_KEY},
timeout=30,
)
if not response.ok:
return JsonResponse({'error': 'Screenshot failed'}, status=502)
content_type = response.headers.get('content-type', 'image/webp')
cache.set(cache_key, {'data': response.content, 'content_type': content_type}, self.CACHE_TTL)
return HttpResponse(response.content, content_type=content_type)
Celery Task for Async Screenshot Generation
For background processing — generating screenshots for reports, sending via email, or storing in S3:
# tasks.py
import requests
import boto3
from celery import shared_task
from django.conf import settings
from django.core.files.base import ContentFile
from .models import PagePreview
@shared_task(bind=True, max_retries=3, default_retry_delay=30)
def generate_screenshot(self, page_preview_id: int, target_url: str):
"""
Generate a screenshot and save to the PagePreview model.
Retries on transient failures.
"""
try:
response = requests.get(
f'{settings.SCREENSHOT_API_BASE}/api/screenshot',
params={'url': target_url, 'format': 'png', 'width': '1200', 'height': '630'},
headers={'X-API-Key': settings.SCREENSHOT_API_KEY},
timeout=30,
)
response.raise_for_status()
preview = PagePreview.objects.get(id=page_preview_id)
preview.screenshot.save(
f'preview_{page_preview_id}.png',
ContentFile(response.content),
save=True,
)
preview.status = 'ready'
preview.save()
except requests.RequestException as exc:
raise self.retry(exc=exc)
except PagePreview.DoesNotExist:
pass # Record deleted before task ran — not an error
Trigger from a view or signal:
# views.py
from .tasks import generate_screenshot
from .models import PagePreview
def create_preview(request):
url = request.POST.get('url')
preview = PagePreview.objects.create(url=url, status='pending')
generate_screenshot.delay(preview.id, url) # runs in background
return JsonResponse({'id': preview.id, 'status': 'pending'})
Django REST Framework Endpoint
For API-first Django projects using DRF:
# serializers.py
from rest_framework import serializers
class ScreenshotRequestSerializer(serializers.Serializer):
url = serializers.URLField()
format = serializers.ChoiceField(choices=['png', 'webp', 'jpeg'], default='webp')
width = serializers.IntegerField(min_value=320, max_value=3840, default=1280)
height = serializers.IntegerField(min_value=240, max_value=2160, default=800)
full_page = serializers.BooleanField(default=False)
delay = serializers.IntegerField(min_value=0, max_value=10000, default=0)
# views.py
import requests
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.http import HttpResponse
from django.conf import settings
from .serializers import ScreenshotRequestSerializer
class ScreenshotAPIView(APIView):
"""
POST /api/screenshots/
Returns screenshot image directly (content-type: image/*).
"""
def post(self, request):
serializer = ScreenshotRequestSerializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
data = serializer.validated_data
params = {
'url': data['url'],
'format': data['format'],
'width': str(data['width']),
'height': str(data['height']),
}
if data['full_page']:
params['full_page'] = 'true'
if data['delay']:
params['delay'] = str(data['delay'])
try:
resp = requests.get(
f'{settings.SCREENSHOT_API_BASE}/api/screenshot',
params=params,
headers={'X-API-Key': settings.SCREENSHOT_API_KEY},
timeout=30,
)
resp.raise_for_status()
except requests.HTTPError as e:
return Response(
{'error': f'Screenshot service error: {e.response.status_code}'},
status=status.HTTP_502_BAD_GATEWAY,
)
except requests.RequestException:
return Response(
{'error': 'Screenshot service unavailable'},
status=status.HTTP_503_SERVICE_UNAVAILABLE,
)
return HttpResponse(
resp.content,
content_type=resp.headers.get('content-type', 'image/webp'),
)
Django Model + Admin Integration
Store screenshots alongside your content models:
# models.py
from django.db import models
class PageSnapshot(models.Model):
STATUS_CHOICES = [
('pending', 'Pending'),
('ready', 'Ready'),
('failed', 'Failed'),
]
url = models.URLField()
screenshot = models.ImageField(upload_to='snapshots/', blank=True)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def capture(self):
"""Synchronous capture — use Celery task for production."""
import requests
from django.conf import settings
from django.core.files.base import ContentFile
resp = requests.get(
f'{settings.SCREENSHOT_API_BASE}/api/screenshot',
params={'url': self.url, 'format': 'png'},
headers={'X-API-Key': settings.SCREENSHOT_API_KEY},
timeout=30,
)
if resp.ok:
self.screenshot.save(f'snapshot_{self.pk}.png', ContentFile(resp.content))
self.status = 'ready'
self.save()
else:
self.status = 'failed'
self.save()
class Meta:
ordering = ['-created_at']
# admin.py
from django.contrib import admin
from django.utils.html import format_html
from .models import PageSnapshot
@admin.register(PageSnapshot)
class PageSnapshotAdmin(admin.ModelAdmin):
list_display = ['url', 'status', 'thumbnail', 'created_at']
readonly_fields = ['screenshot_preview']
def thumbnail(self, obj):
if obj.screenshot:
return format_html(
'<img src="{}" style="height:40px;border-radius:4px;" />',
obj.screenshot.url,
)
return '-'
def screenshot_preview(self, obj):
if obj.screenshot:
return format_html(
'<img src="{}" style="max-width:800px;" />',
obj.screenshot.url,
)
return 'No screenshot yet.'
Management Command for Batch Screenshots
A Django management command for generating or refreshing screenshots in bulk:
# management/commands/capture_snapshots.py
from django.core.management.base import BaseCommand
from django.conf import settings
import requests
import time
class Command(BaseCommand):
help = 'Capture screenshots for all pending PageSnapshots'
def add_arguments(self, parser):
parser.add_argument('--force', action='store_true', help='Recapture even if already ready')
parser.add_argument('--delay', type=float, default=0.5, help='Seconds between requests')
def handle(self, *args, **options):
from myapp.models import PageSnapshot
qs = PageSnapshot.objects.all()
if not options['force']:
qs = qs.filter(status='pending')
self.stdout.write(f'Processing {qs.count()} snapshots...')
for snap in qs.iterator():
try:
snap.capture()
self.stdout.write(f' OK: {snap.url}')
except Exception as e:
self.stdout.write(self.style.ERROR(f' FAIL: {snap.url} — {e}'))
time.sleep(options['delay'])
self.stdout.write(self.style.SUCCESS('Done.'))
Run with: python manage.py capture_snapshots --delay=1.0
Template Tag for Inline Screenshots
A custom template tag for use in Django templates:
# templatetags/screenshots.py
from django import template
from django.conf import settings
from django.utils.html import format_html
from urllib.parse import urlencode
register = template.Library()
@register.simple_tag
def screenshot_url(url, width=1280, height=800, fmt='webp'):
"""Return a URL to the screenshot proxy view."""
params = urlencode({'url': url, 'format': fmt, 'width': width, 'height': height})
return f'/api/screenshot/?{params}'
@register.inclusion_tag('screenshots/preview.html')
def page_preview(url, width=400, height=300):
return {'url': url, 'width': width, 'height': height}
<!-- templates/screenshots/preview.html -->
<img
src="{% screenshot_url url width height 'webp' %}"
width="{{ width }}"
height="{{ height }}"
loading="lazy"
alt="Preview of {{ url }}"
/>
Use in templates:
{% load screenshots %}
{% page_preview "https://example.com" 600 400 %}
Full API reference: /api. Get a free key (50/day): /api/keys.