Django Cloudflare Images Toolkit
A comprehensive Django toolkit for Cloudflare Images with direct creator upload, advanced image management, transformations, and secure upload workflows.
Features
- Direct Creator Upload: Secure image uploads without exposing API keys to clients
- Comprehensive Image Management: Track upload status, metadata, and variants
- Image Usage Registry (SSOT): Automatically tracks which content references each image, surfaces orphans, and powers an admin thumbnail gallery
- Advanced Transformations: Full support for Cloudflare Images transformations
- Template Tags: Easy integration with Django templates
- RESTful API: Complete API for image management
- Webhook Support: Handle Cloudflare webhook notifications
- Management Commands: CLI tools for maintenance and cleanup
- Type Safety: Full type hints throughout the codebase
- Responsive Images: Built-in support for responsive image delivery
Installation
Using uv (Recommended)
# Install uv if you haven't already
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create a new project or navigate to existing one
uv init my-project
cd my-project
# Add django-cloudflareimages-toolkit to your project
uv add django-cloudflareimages-toolkit
# Or install in development mode from source
uv add --editable .
Using pip
pip install django-cloudflareimages-toolkit
Quick Start
1. Add to Django Settings
# settings.py
INSTALLED_APPS = [
# ... your other apps
'rest_framework',
'django_cloudflareimages_toolkit',
]
# Cloudflare Images Configuration
CLOUDFLARE_IMAGES = {
'ACCOUNT_ID': 'your-cloudflare-account-id', # For API calls
'ACCOUNT_HASH': 'your-cloudflare-account-hash', # For delivery URLs (different from ID!)
'API_TOKEN': 'your-cloudflare-api-token',
'BASE_URL': 'https://api.cloudflare.com/client/v4', # Optional
'DEFAULT_EXPIRY_MINUTES': 30, # Optional (2-360 minutes)
'REQUIRE_SIGNED_URLS': True, # Optional
'DEFAULT_METADATA': {'env': 'production'}, # Optional: merged under per-request metadata
'DEFAULT_CREATOR': None, # Optional: default Cloudflare "creator" value
'METADATA_FACTORY': None, # Optional: dotted path to an ImageMetadataFactory (see below)
'WEBHOOK_SECRET': 'your-webhook-secret', # Optional
'MAX_FILE_SIZE_MB': 10, # Optional
# Optional: serve images from an alternate domain instead of imagedelivery.net
'DELIVERY_URL': None, # e.g. 'images.example.com' (None = use imagedelivery.net)
'DELIVERY_PATH_PREFIX': 'cdn-cgi/imagedelivery', # '' for a Worker proxy
'DELIVERY_INCLUDE_ACCOUNT_HASH': True, # False for a Worker proxy
}
# These deployment-time defaults are intended to be env-backed in your project.
# Per-request values always take precedence over the settings defaults.
# Note: ACCOUNT_HASH is found in Cloudflare Images dashboard under "Developer Resources"
# or from any image delivery URL: https://imagedelivery.net/<ACCOUNT_HASH>/...
#
# Custom delivery domains (DELIVERY_URL) are routed through the image URL factory,
# so a single setting changes every URL the toolkit generates. Common shapes:
# - Native custom domain: 'images.example.com'
# -> https://images.example.com/cdn-cgi/imagedelivery/<hash>/<id>/<variant>
# - Worker reverse-proxy: DELIVERY_URL='cdn.example.com',
# DELIVERY_PATH_PREFIX='', DELIVERY_INCLUDE_ACCOUNT_HASH=False
# -> https://cdn.example.com/<id>/<variant>
# See docs/url_factory.rst for details.
# REST Framework (if not already configured)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
2. Add URL Patterns
# urls.py
from django.urls import path, include
urlpatterns = [
# ... your other URLs
path('cloudflare-images/', include('django_cloudflareimages_toolkit.urls')),
]
3. Run Migrations
python manage.py makemigrations django_cloudflareimages_toolkit
python manage.py migrate
Referencing
CloudflareImagefrom your custom user model's app? If the same migration that defines yourAUTH_USER_MODEL(or any model in it) has aForeignKeytoCloudflareImage, see Referencing CloudflareImage from a custom user model below — you need one explicit migration dependency to avoid a circular dependency. Ordinary apps that referenceCloudflareImagefrom a different app than the one defining the user model need no special handling.
4. Django Admin Integration (Optional)
The module includes comprehensive Django admin integration for monitoring and managing images:
# settings.py - Admin is automatically registered when the app is installed
# No additional configuration needed
# To access the admin interface:
# 1. Create a superuser: python manage.py createsuperuser
# 2. Visit /admin/ and navigate to "Cloudflare Images" section
Usage
API Endpoints
Create Upload URL
POST /cloudflare-images/api/upload-url/
Content-Type: application/json
{
"metadata": {"type": "avatar", "user_id": "123"},
"require_signed_urls": true,
"expiry_minutes": 60,
"filename": "avatar.jpg",
"creator": "user-123"
}
Response:
{
"id": "uuid-here",
"cloudflare_id": "cloudflare-image-id",
"upload_url": "https://upload.imagedelivery.net/...",
"expires_at": "2024-01-01T12:00:00Z",
"status": "pending"
}
List / Search Images
GET /cloudflare-images/api/images/
# Filter & search:
GET /cloudflare-images/api/images/?status=uploaded&filename=avatar&creator=user-123
GET /cloudflare-images/api/images/?orphaned=true # only unreferenced images
GET /cloudflare-images/api/images/?search=logo&ordering=-created_at
GET /cloudflare-images/api/images/?metadata__type=avatar
Look Up an Image by Cloudflare ID
GET /cloudflare-images/api/images/by-cloudflare-id/{cloudflare_id}/
Check Image Status
POST /cloudflare-images/api/images/{id}/check_status/
Image Usage (which content references an image)
GET /cloudflare-images/api/images/{id}/usages/ # references for one image
GET /cloudflare-images/api/images/orphans/ # images referenced by nothing
GET /cloudflare-images/api/usages/ # browse all usage records
Delete Images (usage-aware, removes from Cloudflare + DB)
# Refused with HTTP 409 if the image is still referenced by content...
DELETE /cloudflare-images/api/images/{id}/
# ...unless you force it:
DELETE /cloudflare-images/api/images/{id}/?force=true
# Bulk delete by internal id and/or Cloudflare id:
POST /cloudflare-images/api/images/bulk_delete/
{"ids": ["uuid-1"], "cloudflare_ids": ["cf-2"], "force": false}
Get Image Statistics
GET /cloudflare-images/api/stats/
Template Tags
Load the template tags in your templates:
{% load cloudflare_images %}
Basic Image Transformations
<!-- Simple thumbnail -->
{% cf_thumbnail image.public_url 200 %}
<!-- Avatar with transformations -->
{% cf_avatar user.profile_image.public_url 100 %}
<!-- Custom transformations -->
{% cf_image_transform image.public_url width=800 height=600 fit='cover' quality=85 %}
<!-- Hero image -->
{% cf_hero_image banner.public_url 1920 800 %}
Responsive Images
<!-- cf_srcset / cf_responsive_image / cf_sizes return strings (no template needed) -->
<img src="{% cf_responsive_image image.public_url 800 %}"
srcset="{% cf_srcset image.public_url '320,640,1024,1920' %}"
sizes="(max-width: 768px) 100vw, 800px"
alt="Responsive image">
Upload Form
Use a CloudflareImageField on a model and render its form — the bundled widget
ships its own template + static JS/CSS and resolves the upload endpoint
automatically:
from django_cloudflareimages_toolkit.fields import CloudflareImageField
class Product(models.Model):
image = CloudflareImageField(blank=True, null=True)
Note: the convenience inclusion tags
cf_responsive_img,cf_picture,cf_upload_form, andcf_image_galleryare registered, but you must supply their templates under acloudflare_images/template directory (cloudflare_images/responsive_image.html,cloudflare_images/picture_element.html,cloudflare_images/upload_form.html,cloudflare_images/image_gallery.html). The package does not ship them, so calling these tags without first adding those templates raisesTemplateDoesNotExist. The string-returning tags above need no templates.
Python API
Creating Upload URLs
from django_cloudflareimages_toolkit.services import cloudflare_service
# Create upload URL
image = cloudflare_service.create_direct_upload_url(
user=request.user,
metadata={'type': 'product', 'category': 'electronics'},
require_signed_urls=True,
expiry_minutes=60,
creator='user-123', # Cloudflare "creator" field, persisted + queryable
)
print(f"Upload URL: {image.upload_url}")
print(f"Expires at: {image.expires_at}")
Any argument you omit falls back to its settings default (DEFAULT_METADATA,
DEFAULT_CREATOR, REQUIRE_SIGNED_URLS, DEFAULT_EXPIRY_MINUTES). To bypass
DEFAULT_CREATOR for a single upload, pass an explicit empty string
(creator='', or "creator": "" on the REST endpoint). The resolved
metadata and creator are sent to Cloudflare's
/images/v2/direct_upload endpoint and round-tripped onto the local
CloudflareImage record, so they are queryable from Django:
CloudflareImage.objects.filter(creator='user-123')
CloudflareImage.objects.filter(metadata__type='product')
Programmatic metadata (ImageMetadataFactory)
For metadata that must be computed at upload time (tenant id, request context,
timestamps, …), register a server-side factory instead of a static
DEFAULT_METADATA dict. Subclass ImageMetadataFactory and point
METADATA_FACTORY at it (a dotted path, class, instance, or any callable):
# myapp/factories.py
from django_cloudflareimages_toolkit import ImageMetadataFactory
class TenantMetadataFactory(ImageMetadataFactory):
def get_metadata(self, *, metadata, user=None, **context):
if user is not None:
metadata['uploaded_by'] = str(user.pk)
metadata['source'] = 'web'
return metadata
# settings.py
CLOUDFLARE_IMAGES['METADATA_FACTORY'] = 'myapp.factories.TenantMetadataFactory'
The factory receives the already-resolved metadata plus upload context and returns the final dict. Merge precedence is, lowest to highest:
DEFAULT_METADATA < per-request metadata < factory output
Because the factory is trusted server-side code it has the final say and can both augment and override client-supplied keys.
Image Transformations
from django_cloudflareimages_toolkit.transformations import CloudflareImageTransform
# Cloudflare Images (imagedelivery.net) - uses flexible variants
transform = CloudflareImageTransform(image.public_url)
thumbnail_url = (transform
.width(300)
.height(300)
.fit('cover')
.quality(85)
.build())
# Result: https://imagedelivery.net/<hash>/<id>/width=300,height=300,fit=cover,quality=85
# Cloudflare Image Resizing (custom domains) - uses /cdn-cgi/image/ format
transform = CloudflareImageTransform("/images/photo.jpg", zone="example.com")
resized_url = transform.width(800).quality(85).build()
# Result: https://example.com/cdn-cgi/image/width=800,quality=85/images/photo.jpg
# Use predefined variants
from django_cloudflareimages_toolkit.transformations import CloudflareImageVariants
avatar_url = CloudflareImageVariants.avatar(image.public_url, 100)
hero_url = CloudflareImageVariants.hero_image(image.public_url, 1920, 800)
thumbnail_url = CloudflareImageVariants.thumbnail(image.public_url, 150)
product_url = CloudflareImageVariants.product_image(image.public_url, 400)
Checking Image Status
# Check if image is uploaded
if image.is_uploaded:
print(f"Image available at: {image.public_url}")
# Refresh status from Cloudflare
cloudflare_service.check_image_status(image)
Registering an already-uploaded image
When a client finishes a direct upload it reports back a cloudflare_id. Do
not trust that ID by calling
CloudflareImage.objects.get_or_create(cloudflare_id=<id>): the ID may not
exist, may still be a draft (no bytes uploaded yet), or belong to another user,
and get_or_create would happily leave a bare local row with no status or
variants.
Use the manager method instead. It verifies the image against Cloudflare first — confirming it exists and that its draft state is cleared — then creates the local record populated with status, variants, metadata, and creator:
from django_cloudflareimages_toolkit import (
CloudflareImage,
ImageNotFoundError,
ImageNotReadyError,
ImageOwnershipError,
)
try:
image = CloudflareImage.objects.register_uploaded(
cloudflare_id, user=request.user
)
except ImageNotFoundError:
... # the ID does not exist in Cloudflare
except ImageNotReadyError:
... # the image exists but is still a draft (upload not completed)
register_uploaded only creates/returns a local row once Cloudflare confirms a
completed upload, so the resulting record is always trustworthy.
If you set creator at upload time to the uploader's identifier, pass
expected_creator so a caller can only register their own image — the
Cloudflare creator must match or ImageOwnershipError is raised before any
row is created:
try:
image = CloudflareImage.objects.register_uploaded(
cloudflare_id,
user=request.user,
expected_creator=str(request.user.pk),
)
except ImageOwnershipError:
... # the image belongs to a different creator
ImageOwnershipError is also raised if the cloudflare_id is already registered
locally to a different user, so register_uploaded never hands a caller back
someone else's record — even without expected_creator.
Management Commands
Clean Up Expired Images
# Dry run to see what would be cleaned up
python manage.py cleanup_expired_images --dry-run
# Mark expired images as expired
python manage.py cleanup_expired_images
# Delete old expired images (older than 7 days)
python manage.py cleanup_expired_images --delete --days 7
# Delete orphaned (unreferenced) images older than 30 days from Cloudflare + DB
python manage.py cleanup_expired_images --delete-orphans --orphan-days 30
Reconcile the Image Usage Registry
Signals keep usage tracking current for ordinary saves/deletes. Bulk operations
(QuerySet.update(), bulk_create, loaddata) bypass signals, so run this to
rebuild the registry and report orphans / unregistered references. It is
idempotent and safe to schedule:
python manage.py reconcile_image_usage # rebuild + report
python manage.py reconcile_image_usage --dry-run # report only, no writes
Django Admin Interface
The module provides a comprehensive Django admin interface for monitoring and managing Cloudflare Images:
Features:
- Image List View: View all images with status, thumbnails, and key information
- Gallery View: A thumbnail-grid view of uploads (toggle to table) with status, orphan, and usage badges
- Used-by Panel: See which content references each image, with links to the referencing objects
- Detailed Image View: Complete image details with transformation examples
- Status Management: Check status, refresh from Cloudflare, mark as expired
- Bulk Actions: Perform operations on multiple images at once
- Upload Logs: View complete audit trail for each image
- Statistics Dashboard: Overview of upload success rates and system health
- Search & Filtering: Find images by ID, filename, user, status, or date
- Image Previews: Thumbnail previews and full-size image viewing
- Transformation Examples: Live examples of different image transformations
Admin Actions:
- Check Status from Cloudflare: Refresh status for selected images
- Mark as Expired: Manually mark images as expired
- Delete from Cloudflare: Remove images from Cloudflare and local database
- Refresh All Pending/Draft: Update status for all non-final images
Access the Admin:
- Create a superuser:
python manage.py createsuperuser - Visit
/admin/in your browser - Navigate to "Cloudflare Images" section
- Manage images through the intuitive interface
Webhooks
Configure webhooks in your Cloudflare dashboard to point to:
https://yourdomain.com/cloudflare-images/api/webhook/
The webhook endpoint will automatically update image status when uploads complete.
📋 For detailed webhook setup instructions, see the Webhook Configuration documentation
This guide includes:
- Step-by-step Cloudflare dashboard configuration
- Django settings and URL configuration
- Security considerations and signature validation
- Troubleshooting common webhook issues
- Local development setup with ngrok
Advanced Features
Custom Image Variants
from django_cloudflareimages_toolkit.transformations import CloudflareImageTransform
def create_product_variant(image_url: str, size: int = 400) -> str:
"""Create a product image with white background and border."""
return (CloudflareImageTransform(image_url)
.width(size)
.height(size)
.fit('pad')
.background('ffffff')
.border(2, 'cccccc')
.quality(90)
.build())
Responsive Image Sets
from django_cloudflareimages_toolkit.transformations import CloudflareImageUtils
# Generate srcset for responsive images
srcset = CloudflareImageUtils.get_srcset(
image.public_url,
[320, 640, 1024, 1920],
quality=85
)
# Generate sizes attribute
sizes = CloudflareImageUtils.get_sizes_attribute({
'max-width: 768px': 100, # 100vw on mobile
'max-width: 1024px': 50, # 50vw on tablet
'default': 800 # 800px on desktop
})
Bulk Operations
from django_cloudflareimages_toolkit.models import CloudflareImage
# Bulk status check
images = CloudflareImage.objects.filter(status='pending')
for image in images:
try:
cloudflare_service.check_image_status(image)
except Exception as e:
print(f"Failed to check {image.cloudflare_id}: {e}")
Configuration Options
| Setting | Default | Description |
|---|---|---|
ACCOUNT_ID |
Required | Your Cloudflare Account ID (for API calls) |
ACCOUNT_HASH |
Required | Your Cloudflare Account Hash (for delivery URLs - find in Images dashboard) |
API_TOKEN |
Required | Cloudflare API Token with Images permissions |
BASE_URL |
https://api.cloudflare.com/client/v4 |
Cloudflare API base URL |
DEFAULT_EXPIRY_MINUTES |
30 |
Default expiry time for upload URLs (2-360 minutes) |
REQUIRE_SIGNED_URLS |
True |
Require signed URLs by default |
WEBHOOK_SECRET |
None |
Secret for webhook signature validation |
MAX_FILE_SIZE_MB |
10 |
Size accessor exposed as cloudflare_settings.max_file_size_mb; not auto-enforced by the toolkit (Cloudflare applies its own limits) |
DELIVERY_URL |
None |
Alternate delivery domain instead of imagedelivery.net (bare host or full URL) |
DELIVERY_PATH_PREFIX |
cdn-cgi/imagedelivery |
Path prefix after a custom DELIVERY_URL (use '' for a Worker proxy) |
DELIVERY_INCLUDE_ACCOUNT_HASH |
True |
Whether the account hash appears in custom delivery URLs (False for a Worker proxy) |
Models
CloudflareImage
Tracks image uploads and their metadata:
cloudflare_id: Unique Cloudflare image identifieruser: Associated Django user (optional)upload_url: One-time upload URLstatus: Current upload status (pending, draft, uploaded, failed, expired)metadata: Custom metadata JSONvariants: Available image variantsexpires_at: Upload URL expiration time
ImageUploadLog
Tracks events and changes for debugging:
image: Associated CloudflareImageevent_type: Type of event (upload_url_created, status_checked, etc.)message: Human-readable messagedata: Additional event data
ImageUsage
Reverse index mapping each image to the content that references it (see Image Usage Registry):
content_type/object_id/content_object: the referencing model instancefield_name: the field that holds the reference (e.g.avatar, ormanual)cloudflare_id: the referenced Cloudflare image ID (source of truth)image: resolvedCloudflareImage(null = referenced but unregistered)
Referencing CloudflareImage from a custom user model
CloudflareImage.user is a ForeignKey to settings.AUTH_USER_MODEL. That
foreign key lives in migration 0007_cloudflareimage_user, not in
0001_initial — 0001_initial creates the CloudflareImage table with no
dependency on your user model. This is deliberate: it lets you reference
CloudflareImage from the very migration that defines your custom user model.
Most projects need no special handling. You only have to do anything if the
same migration that defines your AUTH_USER_MODEL (or another model created
alongside it) has a ForeignKey to CloudflareImage. In that case Django's
autodetector will, by default, make your migration depend on this app's
latest migration (0007_cloudflareimage_user). Because 0007 in turn
depends on your user model, that creates a circular dependency:
yourapp.0001 -> toolkit.0007 (Django's default: depend on the latest migration)
toolkit.0007 -> yourapp.0001 (0007 adds the FK to AUTH_USER_MODEL)
Django cannot resolve which migration to depend on for you here — only you know
that your user-model migration merely needs the CloudflareImage table to
exist, which happens in 0001_initial. Point the dependency there instead:
class Migration(migrations.Migration):
initial = True
dependencies = [
# Depend on the migration that CREATES CloudflareImage, not the
# auto-generated dependency on the latest toolkit migration. 0001 is
# dependency-free, so this cannot form a circular dependency.
("django_cloudflareimages_toolkit", "0001_initial"),
# ... your other dependencies (auth, contenttypes, etc.)
]
operations = [ ... ]
If makemigrations generated a dependency on 0007_cloudflareimage_user (or
whatever the current latest toolkit migration is), replace it with
0001_initial. The user FK on CloudflareImage is then added afterward by
0007, which runs once your user table exists.
Image Usage Registry (SSOT)
CloudflareImage answers "what has been uploaded". The usage registry answers
the other half — "what content is using each image" — so admins and site staff
have a single source of truth for both.
Automatic tracking. Every model field declared as a CloudflareImageField is
auto-discovered. Saving, updating, or deleting such a model keeps an ImageUsage
row in sync via signals — no extra code required.
from django_cloudflareimages_toolkit.fields import CloudflareImageField
class Product(models.Model):
image = CloudflareImageField(blank=True, null=True)
product = Product.objects.create(image="cloudflare-image-id")
# -> an ImageUsage row now links that image to this product
Manual API. For references the toolkit can't see (an ID kept in a JSON blob, derived at runtime, etc.):
from django_cloudflareimages_toolkit import register_usage, unregister_usage
register_usage(my_object, "cloudflare-image-id") # field_name="manual" by default
unregister_usage(my_object)
Reverse lookups.
image.usages.all() # what uses this image
CloudflareImage.objects.filter(usages__isnull=True) # orphans (unused)
ImageUsage.objects.filter(image__isnull=True) # referenced but unregistered
Admin gallery. The admin image list gains a thumbnail gallery view (with table toggle), status/orphan/usage badges, a "Used by" panel linking to the referencing objects, and Orphaned/Unregistered filters so staff can see at a glance what each image is used by.
Usage-aware deletes (API). The REST API delete endpoints refuse to delete an
image still referenced by content (HTTP 409) unless force=true. The admin's
existing delete actions are not guarded — staff are trusted to consult the
"Used by" panel before deleting.
Bulk operations bypass signals; run
python manage.py reconcile_image_usageto rebuild the registry (it is idempotent).
Development
Setting up with uv
# Clone the repository
git clone https://github.com/Pacficient-Labs/django-cloudflareimages-toolkit.git
cd django-cloudflareimages-toolkit
# Install dependencies
uv sync
# Install development dependencies
uv sync --group dev
# Run tests
uv run pytest
# Format code
uv run black .
uv run isort .
# Type checking
uv run mypy django_cloudflareimages_toolkit
Running Tests
# Run all tests (use venv Python directly for reliability)
.venv/bin/python -m pytest
# Run with coverage
.venv/bin/python -m pytest --cov=django_cloudflareimages_toolkit
# Run specific test file
.venv/bin/python -m pytest tests/test_imports.py
# Alternative: use uv run (ensure venv is synced first)
uv sync --extra dev
uv run pytest
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests for your changes
- Run the test suite (
uv run pytest) - Format your code (
uv run black . && uv run isort .) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
- Documentation: https://django-cloudflareimages-toolkit.readthedocs.io/
- Issues: https://github.com/Pacficient-Labs/django-cloudflareimages-toolkit/issues
- Discussions: https://github.com/Pacficient-Labs/django-cloudflareimages-toolkit/discussions
Changelog
For the full release history with diff links, see GitHub Releases.
v1.1.1
- Docs: Audited the whole documentation set against the code and corrected drift — removed settings that don't exist (
DEFAULT_VARIANT,UPLOAD_TIMEOUT,CLEANUP_EXPIRED_HOURS,ALLOWED_FORMATS), fixed the webhook URL to include theapi/segment, corrected theCloudflareImageTransformexamples (.build(); there is no.draw()/.url()), clarified that thecf_responsive_img/cf_picture/cf_upload_form/cf_image_galleryinclusion tags require caller-supplied templates, and fixed severalusage/apisnippets. No code behavior changed.
v1.1.0
- Image Usage Registry (SSOT): new
ImageUsagemodel + registry tracking which content references each image, auto-discovery ofCloudflareImageFields, signal sync,register_usage/unregister_usage, thereconcile_image_usagecommand, an admin thumbnail gallery, and usage-aware deletes (HTTP 409 unless?force=true). - Configurable delivery URL:
DELIVERY_URL/DELIVERY_PATH_PREFIX/DELIVERY_INCLUDE_ACCOUNT_HASH, plusCloudflareImageURLFactory(image_url_factory) as the single source of truth for delivery URLs. - Configurable upload defaults:
DEFAULT_METADATA,DEFAULT_CREATOR, a pluggableMETADATA_FACTORY, end-to-end Cloudflarecreator, andCloudflareImage.objects.register_uploaded()for safe register-by-ID.
v1.0.13
- Added: New "Patterns & Recipes" docs page (
docs/patterns.rst) with working code for failover/resilience when the Cloudflare Images API is unavailable, and for image-access authorization with role-based permissions and dynamic watermarking. Both are built on the existing service + transformation primitives — the package stays small, the docs show you how to assemble them.
v1.0.12
- Metadata-only release. Corrected Trove classifiers (dropped EOL Django 4.0/4.1/5.0; added 5.1/5.2/6.0 + Python 3.14); promoted Development Status to Production/Stable; added
Typing :: Typedclassifier and shipped the correspondingpy.typedmarker (PEP 561). - Added:
ChangelogandRelease Notesentries to[project.urls]so PyPI's sidebar links straight to GitHub releases. - Docs: Merged the standalone root-level
WEBHOOK_SETUP.mdintodocs/webhooks.rst(now the single source, rendered on Read the Docs). Read the Docs config bumped to Python 3.12, ubuntu-24.04, andfail_on_warning: true. - Repo: Moved
example_usage.py→examples/cloudflareimagefield.pywith anexamples/README.mdindex.
v1.0.11
- Fixed (security):
WebhookView.postpreviously skipped signature validation when either the signature header was absent orWEBHOOK_SECRETwas unset — meaning a caller could omit theX-Signatureheader entirely and bypass authentication on a deployment that thought it was protected. A configured secret now means signatures are required; missing-signature returns 401 before the body is parsed. - Fixed (observability):
WebhookPayloadSerializer.is_valid(raise_exception=True)raises DRFValidationError, which used to be swallowed by a broadexcept Exceptionand reported as500 Internal server error. Malformed payloads are now400 Invalid payload, reserving 5xx for genuinely unexpected processing failures. - Added: Documented status-code matrix for the webhook endpoint (200/400/401/404/500) with the contract for each.
- Tests: Five new regression tests in
tests/test_webhook_view.pycovering both fixes. 41/41 tests pass on Django 4.2/5.0/5.1/5.2/6.0.
v1.0.10
- Fixed: Eager settings validation no longer blocks Django startup when
CLOUDFLARE_IMAGESsettings are absent or incomplete in non-production environments.
v1.0.9
- Fixed: Transformation URLs now use correct Cloudflare format (
width=300,height=200path-based) - Fixed: Added missing
expiryparameter to direct upload API requests - Fixed:
per_pagemax increased to 10000 (was incorrectly 100) - Added:
ACCOUNT_HASHsetting (separate fromACCOUNT_IDfor delivery URLs) - Fixed: Enum comparison for
ImageUploadStatus(was comparing string to enum) - Added:
get_variant_url()method onCloudflareImagemodel - Fixed: Double-slash bug in cdn-cgi URLs for Image Resizing
- Fixed: Lazy imports to prevent import-time Django dependency errors
v1.0.0
- Initial release
- Direct Creator Upload support
- Comprehensive image transformations
- Template tags and filters
- RESTful API
- Webhook support
- Management commands
- Full type safety
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_cloudflareimages_toolkit-1.1.1.tar.gz.
File metadata
- Download URL: django_cloudflareimages_toolkit-1.1.1.tar.gz
- Upload date:
- Size: 293.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
21bcff1f06a4e9f8e7caf03026e0275b6aea475c93589eed7e25cd83e1f6f999
|
|
| MD5 |
914e95c20a15026105d652e5073e96cf
|
|
| BLAKE2b-256 |
3e0c3d74ceeb2f6139674010cc411627d89227a7e5a862350ab95a16afb4deca
|
Provenance
The following attestation bundles were made for django_cloudflareimages_toolkit-1.1.1.tar.gz:
Publisher:
publish.yml on Pacficient-Labs/django-cloudflareimages-toolkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_cloudflareimages_toolkit-1.1.1.tar.gz -
Subject digest:
21bcff1f06a4e9f8e7caf03026e0275b6aea475c93589eed7e25cd83e1f6f999 - Sigstore transparency entry: 2064494759
- Sigstore integration time:
-
Permalink:
Pacficient-Labs/django-cloudflareimages-toolkit@f489e56de646117e9aeaf3543137b0a1c367c1ab -
Branch / Tag:
refs/tags/v1.1.1 - Owner: https://github.com/Pacficient-Labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f489e56de646117e9aeaf3543137b0a1c367c1ab -
Trigger Event:
release
-
Statement type:
File details
Details for the file django_cloudflareimages_toolkit-1.1.1-py3-none-any.whl.
File metadata
- Download URL: django_cloudflareimages_toolkit-1.1.1-py3-none-any.whl
- Upload date:
- Size: 102.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
056b840b99a9c3f5fc88419ec3060b65619b6936ffe76c513cce7fb10a3372c3
|
|
| MD5 |
23af2ff0d3f1264cfa3580dd37314353
|
|
| BLAKE2b-256 |
a3441095e8f77e28bb224c70d3c6a91f89fe3ccc14b8f8d5cd50d0d8e2912e95
|
Provenance
The following attestation bundles were made for django_cloudflareimages_toolkit-1.1.1-py3-none-any.whl:
Publisher:
publish.yml on Pacficient-Labs/django-cloudflareimages-toolkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_cloudflareimages_toolkit-1.1.1-py3-none-any.whl -
Subject digest:
056b840b99a9c3f5fc88419ec3060b65619b6936ffe76c513cce7fb10a3372c3 - Sigstore transparency entry: 2064494871
- Sigstore integration time:
-
Permalink:
Pacficient-Labs/django-cloudflareimages-toolkit@f489e56de646117e9aeaf3543137b0a1c367c1ab -
Branch / Tag:
refs/tags/v1.1.1 - Owner: https://github.com/Pacficient-Labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f489e56de646117e9aeaf3543137b0a1c367c1ab -
Trigger Event:
release
-
Statement type: