API Reference

This section provides detailed documentation for all classes, methods, and functions in django-cloudflareimages-toolkit.

CloudflareImagesService Class

class django_cloudflareimages_toolkit.services.CloudflareImagesService[source]

Bases: object

Service class for Cloudflare Images API operations.

REQUEST_TIMEOUT = 30
__init__()[source]
property account_id: str
property api_token: str
property base_url: str
property session: Session
get_direct_upload_url(user=None, custom_id: str | None = None, metadata: dict[str, Any] | None = None, require_signed_urls: bool | None = None, expiry_minutes: int | None = None, creator: str | None = None) dict[str, str][source]

Get a one-time upload URL for direct creator upload.

This is an alias for create_direct_upload_url that returns a dict to match the documentation examples.

create_direct_upload_url(user=None, custom_id: str | None = None, metadata: dict[str, Any] | None = None, require_signed_urls: bool | None = None, expiry_minutes: int | None = None, creator: str | None = None) CloudflareImage[source]

Create a one-time upload URL for direct creator upload.

Settings-backed defaults are applied for any argument left as None: require_signed_urls and expiry_minutes from their respective settings, creator from DEFAULT_CREATOR, and metadata is merged on top of DEFAULT_METADATA (per-request keys win).

Parameters:
  • user – Django user instance (optional)

  • custom_id – Custom ID for the image (optional)

  • metadata – Additional metadata to store with the image

  • require_signed_urls – Whether to require signed URLs

  • expiry_minutes – Minutes until the upload URL expires

  • creator – Cloudflare creator value to associate with the image

Returns:

CloudflareImage instance with upload URL

Raises:

CloudflareImagesError – If the API request fails

check_image_status(image: CloudflareImage) dict[str, Any][source]

Check the status of an image upload.

Parameters:

image – CloudflareImage instance

Returns:

Dictionary containing the image status data

Raises:

CloudflareImagesError – If the API request fails

list_images(page: int = 1, per_page: int = 1000) dict[str, Any][source]

List images from Cloudflare Images.

Parameters:
  • page – Page number for pagination (default: 1)

  • per_page – Number of images per page (default: 1000; clamped to the Cloudflare maximum, MAX_LIST_PER_PAGE)

Returns:

Dictionary with pagination info and list of images

Raises:

CloudflareImagesError – If the API request fails

get_image(image_id: str) dict[str, Any][source]

Get details for a specific image.

Parameters:

image_id – Cloudflare image ID

Returns:

Dictionary with image details

Raises:

CloudflareImagesError – If the API request fails

register_uploaded_image(cloudflare_id: str, user=None, expected_creator: str | None = None) CloudflareImage[source]

Verify an uploaded image against Cloudflare and persist it locally.

This is the safe alternative to CloudflareImage.objects.get_or_create( cloudflare_id=<client-supplied id>): it fetches the image details from Cloudflare, confirms the image exists and that its draft state is cleared (bytes were actually uploaded), and only then creates/returns the local record with status, variants, and metadata populated from the Cloudflare response.

Parameters:
  • cloudflare_id – The Cloudflare image ID reported by the client.

  • user – Django user to associate with the image (optional).

  • expected_creator – If given, the Cloudflare creator on the image must equal this value or ImageOwnershipError is raised before any local row is created. Use it (e.g. with the uploader’s id) to stop a caller registering another user’s image by submitting an arbitrary id from the same Cloudflare account.

Returns:

The created or updated CloudflareImage instance.

Raises:
update_image(image_id: str, metadata: dict[str, Any] | None = None, require_signed_urls: bool | None = None) dict[str, Any][source]

Update image metadata and settings.

Parameters:
  • image_id – Cloudflare image ID

  • metadata – New metadata for the image

  • require_signed_urls – Whether to require signed URLs

Returns:

Dictionary with updated image details

Raises:

CloudflareImagesError – If the API request fails

delete_image(image: CloudflareImage, *, missing_ok: bool = False) bool[source]

Delete an image from Cloudflare Images.

A Cloudflare 404 (the image is already absent) is surfaced as the typed ImageNotFoundError, mirroring get_image(). Because ImageNotFoundError subclasses CloudflareImagesError, existing except CloudflareImagesError callers keep matching it.

Parameters:
  • image – CloudflareImage instance

  • missing_ok – When True, a Cloudflare 404 (image already gone) is treated as a successful delete and returns True instead of raising. The desired end state for a delete is “not in Cloudflare”, so callers whose job is to converge on that state (orphan cleanup, the admin delete action, the viewset delete) pass missing_ok=True and remain idempotent across repeated or partially-failed runs.

Returns:

True if deletion was successful (or the image was already absent and missing_ok is set).

Raises:
  • ImageNotFoundError – Cloudflare returned 404 and missing_ok is False.

  • CloudflareImagesError – For any other API/transport failure.

validate_webhook_signature(payload: bytes, signature: str) bool[source]

Validate webhook signature from Cloudflare.

Parameters:
  • payload – Raw webhook payload

  • signature – Signature from webhook headers (should be in format ‘sha256=…’)

Returns:

True if signature is valid

process_webhook(payload: dict[str, Any]) CloudflareImage | None[source]

Process webhook payload from Cloudflare.

Returns the updated CloudflareImage when the payload matches a known image, or None only for the genuine unknown image case — a missing id or no matching local row. The caller (WebhookView) maps None to a 404.

Crucially, this does NOT swallow unexpected errors. A transient failure (e.g. a DB hiccup, or a save error inside update_from_cloudflare_response) is allowed to propagate so the view’s existing 500 path runs and Cloudflare retries delivery. The previous catch-all except Exception: return None reported such recoverable errors as a 404 (“no such image, don’t retry”) — the opposite of what idempotent webhook delivery needs, and it made the view’s 500 branch unreachable.

Parameters:

payload – Webhook payload data

Returns:

Updated CloudflareImage instance, or None if the image is unknown.

Raises:

Exception – Any unexpected error while processing a known image is propagated to the caller (surfaced as a 500 so Cloudflare retries).

The main service class for interacting with Cloudflare Images API.

get_direct_upload_url Method

CloudflareImagesService.get_direct_upload_url(user=None, custom_id: str | None = None, metadata: dict[str, Any] | None = None, require_signed_urls: bool | None = None, expiry_minutes: int | None = None, creator: str | None = None) dict[str, str][source]

Get a one-time upload URL for direct creator upload.

This is an alias for create_direct_upload_url that returns a dict to match the documentation examples.

Parameters:

  • metadata (dict, optional): Custom metadata to attach to the image

  • require_signed_urls (bool, optional): Whether to require signed URLs for access

  • creator (str, optional): Cloudflare “creator” value to associate with the upload

Any argument left as None falls back to its settings default (DEFAULT_METADATA, REQUIRE_SIGNED_URLS, DEFAULT_CREATOR).

Returns: dict with ‘id’ and ‘uploadURL’ keys

Raises:

  • CloudflareImagesError: When the API request fails, or the Cloudflare response reports success: false

  • ValueError: When a required setting (ACCOUNT_ID, API_TOKEN) is missing

Example:

from django_cloudflareimages_toolkit.services import CloudflareImagesService

service = CloudflareImagesService()
upload_data = service.get_direct_upload_url(
    metadata={'category': 'profile', 'user_id': '123'}
)
print(f"Upload URL: {upload_data['uploadURL']}")

list_images Method

CloudflareImagesService.list_images(page: int = 1, per_page: int = 1000) dict[str, Any][source]

List images from Cloudflare Images.

Parameters:
  • page – Page number for pagination (default: 1)

  • per_page – Number of images per page (default: 1000; clamped to the Cloudflare maximum, MAX_LIST_PER_PAGE)

Returns:

Dictionary with pagination info and list of images

Raises:

CloudflareImagesError – If the API request fails

Parameters:

  • page (int, optional): Page number for pagination (default: 1)

  • per_page (int, optional): Number of images per page (default: 1000, max: 10000)

Returns: dict with pagination info and list of images

Example:

images = service.list_images(page=1, per_page=20)
for image in images['result']['images']:
    print(f"Image ID: {image['id']}")

get_image Method

CloudflareImagesService.get_image(image_id: str) dict[str, Any][source]

Get details for a specific image.

Parameters:

image_id – Cloudflare image ID

Returns:

Dictionary with image details

Raises:

CloudflareImagesError – If the API request fails

Parameters:

  • image_id (str, required): Cloudflare image ID

Returns: dict with image details

Raises:

  • ImageNotFoundError: When Cloudflare returns a 404 for the image ID

Example:

image_details = service.get_image('your-image-id')
print(f"Image URL: {image_details['result']['variants'][0]}")

delete_image Method

CloudflareImagesService.delete_image(image: CloudflareImage, *, missing_ok: bool = False) bool[source]

Delete an image from Cloudflare Images.

A Cloudflare 404 (the image is already absent) is surfaced as the typed ImageNotFoundError, mirroring get_image(). Because ImageNotFoundError subclasses CloudflareImagesError, existing except CloudflareImagesError callers keep matching it.

Parameters:
  • image – CloudflareImage instance

  • missing_ok – When True, a Cloudflare 404 (image already gone) is treated as a successful delete and returns True instead of raising. The desired end state for a delete is “not in Cloudflare”, so callers whose job is to converge on that state (orphan cleanup, the admin delete action, the viewset delete) pass missing_ok=True and remain idempotent across repeated or partially-failed runs.

Returns:

True if deletion was successful (or the image was already absent and missing_ok is set).

Raises:
  • ImageNotFoundError – Cloudflare returned 404 and missing_ok is False.

  • CloudflareImagesError – For any other API/transport failure.

Parameters:

  • image_id (str, required): Cloudflare image ID

Returns: dict with success status

Example:

result = service.delete_image('your-image-id')
if result['success']:
    print("Image deleted successfully")

update_image Method

CloudflareImagesService.update_image(image_id: str, metadata: dict[str, Any] | None = None, require_signed_urls: bool | None = None) dict[str, Any][source]

Update image metadata and settings.

Parameters:
  • image_id – Cloudflare image ID

  • metadata – New metadata for the image

  • require_signed_urls – Whether to require signed URLs

Returns:

Dictionary with updated image details

Raises:

CloudflareImagesError – If the API request fails

Parameters:

  • image_id (str, required): Cloudflare image ID

  • metadata (dict, optional): New metadata for the image

  • require_signed_urls (bool, optional): Whether to require signed URLs

Returns: dict with updated image details

Example:

updated = service.update_image(
    'your-image-id',
    metadata={'updated': True, 'category': 'featured'}
)

CloudflareImage Model

class django_cloudflareimages_toolkit.models.CloudflareImage(*args, **kwargs)[source]

Bases: Model

Model to track Cloudflare image uploads.

objects = <django_cloudflareimages_toolkit.models.CloudflareImageManager object>
id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

cloudflare_id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

user

Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Child.parent is a ForwardManyToOneDescriptor instance.

filename

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

original_filename

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

content_type

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

file_size

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

upload_url

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

status

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

require_signed_urls

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

metadata

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

creator

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

created_at

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

updated_at

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

uploaded_at

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

expires_at

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

width

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

height

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

format

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

variants

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

cloudflare_metadata

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

last_referenced_at

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

property is_expired: bool

Check if the upload URL has expired.

property is_uploaded: bool

Check if the image has been successfully uploaded.

property public_url: str | None

Get the public variant URL for the uploaded image.

property thumbnail_url: str | None

Get the thumbnail variant URL for the uploaded image.

get_variant_url(variant_name: str) str | None[source]

Get the URL for a specific variant by name.

Cloudflare returns variants as full URLs like: https://imagedelivery.net/<hash>/<id>/<variant_name>

Parameters:

variant_name – The variant name to look for (e.g., ‘public’, ‘thumbnail’)

Returns:

The full variant URL if found, None otherwise

property is_ready: bool

Check if the image is ready for use (uploaded and processed).

get_url(variant: str = 'public') str | None[source]

Get the URL for a specific variant of the image.

Parameters:

variant – The variant name (e.g., ‘public’, ‘thumbnail’, ‘avatar’)

Returns:

The URL for the specified variant, or None if not found

get_signed_url(variant: str = 'public', expiry: int = 3600) str | None[source]

Get a signed URL for a specific variant of the image.

Parameters:
  • variant – The variant name (e.g., ‘public’, ‘thumbnail’, ‘avatar’)

  • expiry – Expiry time in seconds (default: 3600 = 1 hour)

Returns:

A signed URL for the specified variant, or None if not available

Note

This method requires the image to have require_signed_urls=True and proper Cloudflare API integration for signing URLs.

update_from_cloudflare_response(response_data: dict[str, Any]) None[source]

Update model fields from Cloudflare API response.

exception DoesNotExist

Bases: ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: MultipleObjectsReturned

exception NotUpdated

Bases: ObjectNotUpdated, DatabaseError

get_next_by_created_at(*, field=<django.db.models.fields.DateTimeField: created_at>, is_next=True, **kwargs)
get_next_by_expires_at(*, field=<django.db.models.fields.DateTimeField: expires_at>, is_next=True, **kwargs)
get_next_by_updated_at(*, field=<django.db.models.fields.DateTimeField: updated_at>, is_next=True, **kwargs)
get_previous_by_created_at(*, field=<django.db.models.fields.DateTimeField: created_at>, is_next=False, **kwargs)
get_previous_by_expires_at(*, field=<django.db.models.fields.DateTimeField: expires_at>, is_next=False, **kwargs)
get_previous_by_updated_at(*, field=<django.db.models.fields.DateTimeField: updated_at>, is_next=False, **kwargs)
get_status_display(*, field=<django.db.models.fields.CharField: status>)
logs

Accessor to the related objects manager on the reverse side of a many-to-one relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Parent.children is a ReverseManyToOneDescriptor instance.

Most of the implementation is delegated to a dynamically defined manager class built by create_reverse_many_to_one_manager() defined below.

usages

Accessor to the related objects manager on the reverse side of a many-to-one relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Parent.children is a ReverseManyToOneDescriptor instance.

Most of the implementation is delegated to a dynamically defined manager class built by create_reverse_many_to_one_manager() defined below.

user_id

Django model for tracking Cloudflare Images.

Fields:

  • cloudflare_id (CharField): Unique Cloudflare image ID (max 255 characters)

  • filename (CharField): Original filename (max 255 characters)

  • uploaded_at (DateTimeField): Timestamp when image was uploaded

  • file_size (PositiveIntegerField): File size in bytes (optional)

  • width (PositiveIntegerField): Image width in pixels (optional)

  • height (PositiveIntegerField): Image height in pixels (optional)

  • format (CharField): Image format (jpeg, png, gif, webp) (max 10 characters)

  • variants (JSONField): Available image variants

  • metadata (JSONField): Custom metadata

  • creator (CharField): Cloudflare “creator” value, indexed and queryable (max 255 characters, blank allowed)

  • status (CharField): Upload status — one of pending, draft, uploaded, failed, expired

  • upload_url (URLField): Direct upload URL (temporary)

  • expires_at (DateTimeField): When the upload URL expires

is_ready, is_uploaded, is_expired, public_url, and thumbnail_url are read-only properties, not stored fields.

Methods:

get_url Method

CloudflareImage.get_url(variant: str = 'public') str | None[source]

Get the URL for a specific variant of the image.

Parameters:

variant – The variant name (e.g., ‘public’, ‘thumbnail’, ‘avatar’)

Returns:

The URL for the specified variant, or None if not found

Parameters:

  • variant (str, optional): Image variant name (default: ‘public’)

Returns: str - Full image URL

Example:

image = CloudflareImage.objects.get(cloudflare_id='your-id')
original_url = image.get_url()
thumbnail_url = image.get_url('thumbnail')

get_variant_url Method

CloudflareImage.get_variant_url(variant_name: str) str | None[source]

Get the URL for a specific variant by name.

Cloudflare returns variants as full URLs like: https://imagedelivery.net/<hash>/<id>/<variant_name>

Parameters:

variant_name – The variant name to look for (e.g., ‘public’, ‘thumbnail’)

Returns:

The full variant URL if found, None otherwise

Parameters:

  • variant_name (str, required): Name of the variant to retrieve

Returns: str or None - Full variant URL if found, None otherwise

Retrieves a specific variant URL from the image’s variants list. This method searches through the stored variants (which may be a list of URLs or a dict) to find and return the URL matching the requested variant name.

Example:

image = CloudflareImage.objects.get(cloudflare_id='your-id')

# Get specific variant URLs
public_url = image.get_variant_url('public')
thumbnail_url = image.get_variant_url('thumbnail')

# Returns None if variant doesn't exist
custom_url = image.get_variant_url('nonexistent')  # None

get_signed_url Method

CloudflareImage.get_signed_url(variant: str = 'public', expiry: int = 3600) str | None[source]

Get a signed URL for a specific variant of the image.

Parameters:
  • variant – The variant name (e.g., ‘public’, ‘thumbnail’, ‘avatar’)

  • expiry – Expiry time in seconds (default: 3600 = 1 hour)

Returns:

A signed URL for the specified variant, or None if not available

Note

This method requires the image to have require_signed_urls=True and proper Cloudflare API integration for signing URLs.

Parameters:

  • variant (str, optional): Image variant name

  • expiry (int, optional): URL expiry time in seconds

Returns: str - Signed image URL

Example:

# Get signed URL that expires in 1 hour
signed_url = image.get_signed_url('thumbnail', expiry=3600)

is_expired Property

CloudflareImage.is_expired

Check if the upload URL has expired.

Returns: bool - True if upload URL has expired

Example:

if image.is_expired:
    print("Upload URL has expired")

Manager Methods

register_uploaded Method

objects.register_uploaded(cloudflare_id: str, user=None, expected_creator: str | None = None) CloudflareImage

Safely register an already-uploaded image by its cloudflare_id.

Unlike get_or_create(cloudflare_id=...) with a client-supplied ID, this verifies the image against Cloudflare first: it confirms the image exists and that its draft state is cleared (bytes actually uploaded) before creating/returning the local record, then populates status, variants, and metadata from the Cloudflare response.

Parameters:
  • cloudflare_id – The Cloudflare image ID reported by the client.

  • user – Django user to associate with the image (optional).

  • expected_creator – If given, the Cloudflare creator on the image must equal this value (otherwise ImageOwnershipError is raised before any local row is created). Pass the uploader’s id here when you set creator at upload time to enforce that a caller can only register their own image.

Returns:

The created or updated CloudflareImage instance.

Raises:

Safely registers a client-supplied cloudflare_id. The manager fetches the image from Cloudflare, confirms it exists and that its draft state is cleared, then creates (or returns) the local record populated with status, variants, metadata, and creator. No local row is created on failure.

Parameters:

  • cloudflare_id (str, required): Cloudflare image ID reported by the client

  • user (optional): User to associate with the local record

  • expected_creator (str, optional): When given, the Cloudflare creator must equal it or ImageOwnershipError is raised before any row is created

Returns: CloudflareImage - The created or existing local record

Raises:

  • ImageNotFoundError: When the image does not exist in Cloudflare

  • ImageNotReadyError: When the image exists but is still a draft

  • ImageOwnershipError: When expected_creator does not match the image’s creator, or when the cloudflare_id is already registered locally to a different user

  • CloudflareImagesError: When the cloudflare_id is longer than the local column (255), or for other Cloudflare API failures

Example:

from django_cloudflareimages_toolkit import (
    CloudflareImage, ImageNotFoundError, ImageNotReadyError, ImageOwnershipError,
)

try:
    image = CloudflareImage.objects.register_uploaded(
        cloudflare_id,
        user=request.user,
        expected_creator=str(request.user.pk),  # optional ownership gate
    )
except ImageNotFoundError:
    ...  # id does not exist in Cloudflare
except ImageNotReadyError:
    ...  # exists but upload not completed (still a draft)
except ImageOwnershipError:
    ...  # image belongs to a different creator

Warning

Calling CloudflareImage.objects.get_or_create(cloudflare_id=<client value>) directly is unsafe — the id may not exist, may be a draft, or may belong to another user, and it leaves a bare local row. Use register_uploaded instead.

ImageUsage Model

class django_cloudflareimages_toolkit.models.ImageUsage(*args, **kwargs)[source]

Bases: Model

Reverse index: which content object references which Cloudflare image.

This is the missing half of the toolkit’s source of truth. CloudflareImage records what has been uploaded; ImageUsage records where each image is used — the model instance and field that point at a given cloudflare_id.

It is a derived index, not an independent source of truth: it is maintained automatically by signals on host models (see registry/signals) and can always be rebuilt from those models with the reconcile_image_usage management command.

Two reverse-lookup helpers fall out of the schema:

  • orphaned images — CloudflareImage.objects.filter(usages__isnull=True) (uploaded but referenced by no content), and

  • unregistered references — ImageUsage.objects.filter(image__isnull=True) (content points at an image the toolkit has no CloudflareImage row for).

content_type

Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Child.parent is a ForwardManyToOneDescriptor instance.

object_id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

content_object

Provide a generic many-to-one relation through the content_type and object_id fields.

This class also doubles as an accessor to the related object (similar to ForwardManyToOneDescriptor) by adding itself as a model attribute.

field_name

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

SOURCE_AUTO = 'auto'
SOURCE_MANUAL = 'manual'
SOURCE_CHOICES = [('auto', 'Auto'), ('manual', 'Manual')]
source

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

cloudflare_id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

image

Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Child.parent is a ForwardManyToOneDescriptor instance.

created_at

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

updated_at

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

property is_unregistered: bool

True when the referenced image has no CloudflareImage record.

exception DoesNotExist

Bases: ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: MultipleObjectsReturned

exception NotUpdated

Bases: ObjectNotUpdated, DatabaseError

content_type_id
get_next_by_created_at(*, field=<django.db.models.fields.DateTimeField: created_at>, is_next=True, **kwargs)
get_next_by_updated_at(*, field=<django.db.models.fields.DateTimeField: updated_at>, is_next=True, **kwargs)
get_previous_by_created_at(*, field=<django.db.models.fields.DateTimeField: created_at>, is_next=False, **kwargs)
get_previous_by_updated_at(*, field=<django.db.models.fields.DateTimeField: updated_at>, is_next=False, **kwargs)
get_source_display(*, field=<django.db.models.fields.CharField: source>)
id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

image_id
objects = <django.db.models.manager.Manager object>

Reverse index recording which content references which Cloudflare image. It is a derived index maintained by signals and rebuildable with reconcile_image_usage. See Usage for the full registry workflow.

Key fields:

  • content_type / object_id / content_object: the referencing model instance

  • field_name: the field holding the reference (e.g. avatar) or manual

  • cloudflare_id: the referenced Cloudflare image ID (source of truth)

  • image: resolved CloudflareImage (None marks an unregistered reference)

Reverse lookups:

image.usages.all()                                    # what references this image
CloudflareImage.objects.filter(usages__isnull=True)   # orphaned (unused) images
ImageUsage.objects.filter(image__isnull=True)         # referenced but unregistered

Image Usage Registry

django_cloudflareimages_toolkit.registry.get_models_with_image_fields(refresh: bool = False) dict[source]

Return {model_class: [field_name, ...]} for every CloudflareImageField.

This is the single source of truth for where images can live, derived from the model definitions themselves so it never drifts from the code. Results are cached; pass refresh=True to rebuild (useful in tests that declare models after the app registry is first inspected).

django_cloudflareimages_toolkit.registry.register_usage(obj, cloudflare_id: str, field_name: str = 'manual')[source]

Manually record that obj references cloudflare_id.

Use this for references the toolkit cannot discover automatically — an image ID kept in a JSONField, fetched from another service, or derived at runtime rather than stored in a CloudflareImageField. Idempotent: calling again for the same (obj, field_name) updates the row in place.

Parameters:
  • obj – Any saved model instance that “owns” the reference.

  • cloudflare_id – The Cloudflare image ID being referenced.

  • field_name – A label distinguishing this reference from others on the same object (defaults to "manual"). It must NOT match the name of a CloudflareImageField declared on obj’s model: usage rows are unique on (content_type, object_id, field_name), so a colliding label would share a row with the auto-tracked field and the two references would overwrite each other.

Returns:

The created or updated ImageUsage row.

Raises:

ValueError – If field_name collides with a tracked field on the model.

django_cloudflareimages_toolkit.registry.unregister_usage(obj, field_name: str = 'manual') None[source]

Remove a manually-registered usage for obj (idempotent).

register_usage / unregister_usage are also importable from the package root. Use them for references the toolkit cannot discover automatically:

from django_cloudflareimages_toolkit import register_usage, unregister_usage

register_usage(obj, "cloudflare-image-id")   # field_name="manual" by default
unregister_usage(obj)

ImageMetadataFactory

class django_cloudflareimages_toolkit.ImageMetadataFactory[source]

Bases: object

Base class for programmatically generating upload metadata.

Subclass and override get_metadata(). Instances are callable, so the service layer can treat a factory instance and a plain callable uniformly.

Example:

from django_cloudflareimages_toolkit.metadata 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",
}
get_metadata(*, metadata: dict[str, Any], user: Any = None, custom_id: str | None = None, creator: str | None = None, **context: Any) dict[str, Any][source]

Return the final metadata dict for an upload.

Parameters:
  • metadata – The resolved metadata so far (DEFAULT_METADATA merged with the per-request metadata).

  • user – The Django user associated with the upload, if any.

  • custom_id – The custom Cloudflare image ID, if provided.

  • creator – The resolved Cloudflare creator value, if any.

  • **context – Forward-compatible additional context.

Returns:

The metadata dict to send to Cloudflare. The base implementation returns metadata unchanged.

Pluggable factory for computing the metadata attached to each upload. Subclass it and override get_metadata; instances are callable. Configure via the METADATA_FACTORY setting.

Method to override:

def get_metadata(self, *, metadata, user=None, custom_id=None,
                 creator=None, **context) -> dict

The factory receives the already-resolved metadata (DEFAULT_METADATA merged with per-request metadata) plus upload context, and returns the final metadata dict that is sent to Cloudflare and persisted. Merge precedence, lowest to highest, is DEFAULT_METADATA < per-request metadata < factory output.

Example:

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

CloudflareImageField

class django_cloudflareimages_toolkit.fields.CloudflareImageField(variants: list[str] | None = None, metadata: dict[str, Any] | None = None, require_signed_urls: bool = False, max_file_size: int | None = None, allowed_formats: list[str] | None = None, **kwargs)[source]

Bases: Field

A Django model field for storing Cloudflare Images.

This field stores the Cloudflare image ID and provides easy access to image URLs, variants, and metadata. It integrates with the CloudflareImage model and service layer.

description = 'Cloudflare Image'
__init__(variants: list[str] | None = None, metadata: dict[str, Any] | None = None, require_signed_urls: bool = False, max_file_size: int | None = None, allowed_formats: list[str] | None = None, **kwargs)[source]

Initialize the CloudflareImageField.

Parameters:
  • variants – List of variant names to create for uploaded images

  • metadata – Default metadata to attach to uploaded images

  • require_signed_urls – Whether to require signed URLs for image access

  • max_file_size – Maximum file size in bytes (None for no limit)

  • allowed_formats – List of allowed image formats (jpeg, png, gif, webp)

  • **kwargs – Additional field options

get_internal_type() str[source]

Return the internal field type for Django.

to_python(value: Any) CloudflareImageFieldValue | None[source]

Convert the database value to a Python object.

Parameters:

value – The value from the database (Cloudflare image ID)

Returns:

CloudflareImageFieldValue instance or None

from_db_value(value: Any, expression, connection) CloudflareImageFieldValue | None[source]

Convert database value to Python object.

get_prep_value(value: Any) str | None[source]

Convert Python object to database value.

Parameters:

value – CloudflareImageFieldValue instance or string

Returns:

Cloudflare image ID string or None

formfield(**kwargs) Field[source]

Return the form field for this model field.

validate(value: Any, model_instance) None[source]

Validate the field value.

deconstruct() tuple[source]

Return field definition for migrations.

Django model field for Cloudflare Images integration.

Parameters:

  • variants (list, optional): List of variant names to create

  • metadata (dict, optional): Default metadata for uploads

  • require_signed_urls (bool, optional): Whether to require signed URLs

  • max_file_size (int, optional): Maximum file size in bytes

  • allowed_formats (list, optional): List of allowed image formats

Example:

from django.db import models
from django_cloudflareimages_toolkit.fields import CloudflareImageField

class Product(models.Model):
    name = models.CharField(max_length=100)
    image = CloudflareImageField(
        variants=['thumbnail', 'large'],
        metadata={'category': 'product'},
        max_file_size=5 * 1024 * 1024,  # 5MB
        allowed_formats=['jpeg', 'png']
    )

CloudflareImageWidget

class django_cloudflareimages_toolkit.widgets.CloudflareImageWidget(variants: list[str] | None = None, metadata: dict[str, Any] | None = None, require_signed_urls: bool = False, max_file_size: int | None = None, allowed_formats: list[str] | None = None, attrs: dict[str, Any] | None = None)[source]

Bases: TextInput

A widget for handling Cloudflare image uploads.

This widget renders a hidden input (which stores the Cloudflare image id) alongside a file input that drives the direct-upload flow implemented in the accompanying static JavaScript. The behaviour is loaded via the widget’s Media; this class only builds the configuration the template and script consume.

template_name = 'django_cloudflareimages_toolkit/widgets/cloudflare_image_widget.html'
__init__(variants: list[str] | None = None, metadata: dict[str, Any] | None = None, require_signed_urls: bool = False, max_file_size: int | None = None, allowed_formats: list[str] | None = None, attrs: dict[str, Any] | None = None)[source]

Initialize the widget.

Parameters:
  • variants – List of image variants to create

  • metadata – Default metadata for uploads

  • require_signed_urls – Whether to require signed URLs

  • max_file_size – Maximum file size in bytes

  • allowed_formats – List of allowed image formats

  • attrs – Additional HTML attributes

format_value(value)[source]

Format the field value for display.

get_context(name: str, value: Any, attrs: dict[str, Any] | None = None) dict[str, Any][source]

Build the template context, assembling the config payload once.

render(name: str, value: Any, attrs: dict[str, Any] | None = None, renderer=None) SafeString[source]

Render the widget HTML from the template.

Falls back to a minimal, equivalent markup block only if the package’s templates aren’t reachable on the loader path.

class Media[source]

Bases: object

Define media files for the widget.

css = {'all': ('django_cloudflareimages_toolkit/css/cloudflare_image_widget.css',)}
js = ('django_cloudflareimages_toolkit/js/cloudflare_image_widget.js',)
property media

Django form widget for handling Cloudflare image uploads with JavaScript-based upload functionality.

Django Admin Integration

class django_cloudflareimages_toolkit.admin.CloudflareImageAdmin(model, admin_site)[source]

Bases: ModelAdmin

Admin interface for CloudflareImage model.

change_list_template = 'admin/django_cloudflareimages_toolkit/cloudflareimage/change_list.html'
list_display = ('cloudflare_id_display', 'user_display', 'status_display', 'filename_display', 'file_size_display', 'usage_count_display', 'created_at', 'expires_at', 'is_expired_display', 'thumbnail_preview', 'actions_display')
list_filter = ('status', <class 'django_cloudflareimages_toolkit.admin.OrphanedImageFilter'>, 'require_signed_urls', 'created_at', 'uploaded_at', 'expires_at', ('user', <class 'django.contrib.admin.filters.RelatedOnlyFieldListFilter'>))
search_fields = ('cloudflare_id', 'filename', 'original_filename', 'user__username', 'user__email')
readonly_fields = ('id', 'cloudflare_id', 'upload_url_display', 'status', 'created_at', 'updated_at', 'uploaded_at', 'expires_at', 'width', 'height', 'format', 'variants_display', 'cloudflare_metadata_display', 'is_expired_display', 'is_uploaded_display', 'is_ready_display', 'public_url_display', 'thumbnail_url_display', 'image_preview', 'transformation_examples')
fields = ('id', 'cloudflare_id', 'user', 'filename', 'original_filename', 'content_type', 'file_size', 'width', 'height', 'format', 'upload_url_display', 'status', 'require_signed_urls', 'metadata', 'created_at', 'updated_at', 'uploaded_at', 'expires_at', 'variants_display', 'cloudflare_metadata_display', 'is_expired_display', 'is_uploaded_display', 'is_ready_display', 'public_url_display', 'thumbnail_url_display', 'image_preview', 'transformation_examples')
inlines = [<class 'django_cloudflareimages_toolkit.admin.ImageUsageInline'>, <class 'django_cloudflareimages_toolkit.admin.ImageUploadLogInline'>]
actions = ['check_status_action', 'mark_as_expired', 'delete_from_cloudflare_action', 'refresh_all_status']
list_per_page = 25
date_hierarchy = 'created_at'
get_queryset(request)[source]

Optimize queryset and annotate the usage count for list/gallery.

changelist_view(request, extra_context=None)[source]

Expose the gallery/table view mode and a clean querystring.

cloudflare_id_display(obj)[source]

Display Cloudflare ID with copy button.

user_display(obj)[source]

Display user with link to user admin.

status_display(obj)[source]

Display status with color coding.

usage_count_display(obj)[source]

Show how many content objects reference this image (orphan if 0).

filename_display(obj)[source]

Display filename with truncation.

file_size_display(obj)[source]

Display file size in human readable format.

is_expired_display(obj)[source]

Display expiry status with icon.

thumbnail_preview(obj)[source]

Display thumbnail preview if available.

actions_display(obj)[source]

Display action buttons.

A per-row “Check status” link used to live here, but it called a client-side checkStatus() that posted to a non-existent admin route (wrong app label, unregistered URL) and always 404’d. Per-image status checks are available via the bulk “Check status from Cloudflare” action instead, so the dead link was removed (see issue #22).

upload_url_display(obj)[source]

Display upload URL with security.

variants_display(obj)[source]

Display available variants.

cloudflare_metadata_display(obj)[source]

Display Cloudflare metadata.

is_uploaded_display(obj)[source]

Display upload status.

is_ready_display(obj)[source]

Display ready status.

public_url_display(obj)[source]

Display public URL with link.

thumbnail_url_display(obj)[source]

Display thumbnail URL with link.

image_preview(obj)[source]

Display larger image preview.

transformation_examples(obj)[source]

Display transformation examples.

check_status_action(request, queryset)[source]

Check status for selected images.

mark_as_expired(request, queryset)[source]

Mark selected images as expired.

delete_from_cloudflare_action(request, queryset)[source]

Delete selected images from Cloudflare.

refresh_all_status(request, queryset)[source]

Refresh status for all non-final status images.

class Media[source]

Bases: object

js = ('admin/js/cloudflare_images_admin.js',)
css = {'all': ('admin/css/cloudflare_images_admin.css',)}
property media

Django admin interface for managing Cloudflare Images.

Features:

  • List view with image previews and metadata

  • Search functionality by filename and Cloudflare ID

  • Filtering by upload status, format, and date

  • Bulk delete operations

  • Image detail view with full metadata

Admin Actions:

  • delete_selected_images: Delete images from both Django and Cloudflare

  • refresh_image_metadata: Refresh metadata from Cloudflare API

  • generate_upload_urls: Generate new upload URLs for failed uploads

Example Customization:

from django.contrib import admin
from django_cloudflareimages_toolkit.admin import CloudflareImageAdmin
from django_cloudflareimages_toolkit.models import CloudflareImage

@admin.register(CloudflareImage)
class CustomCloudflareImageAdmin(CloudflareImageAdmin):
    list_display = ['filename', 'uploaded_at', 'file_size', 'is_ready', 'image_preview']
    list_filter = ['is_ready', 'format', 'uploaded_at']

Webhook Views

class django_cloudflareimages_toolkit.views.WebhookView(**kwargs)[source]

Bases: APIView

API view for handling Cloudflare webhooks.

permission_classes = []
post(request: HttpRequest) HttpResponse[source]

Handle incoming webhook from Cloudflare.

Status codes returned:
  • 200 — payload processed and matched an existing image

  • 400 — payload failed JSON parse OR schema validation

  • 401 — webhook_secret is configured but the request was

    unauthenticated (missing or invalid signature)

  • 404 — payload was valid but referenced an unknown image

  • 500 — unexpected error while processing a validated payload

Note that 401 is only emitted when a CLOUDFLARE_IMAGES.WEBHOOK_SECRET is configured. Deployments without a secret accept any well-formed payload — callers that want enforcement MUST set the secret.

dispatch(request, *args, **kwargs)

.dispatch() is pretty much the same as Django’s regular dispatch, but with extra hooks for startup, finalize, and exception handling.

Handles Cloudflare Images webhook notifications. CSRF-exempt at the view layer; security is enforced via HMAC signature verification when CLOUDFLARE_IMAGES["WEBHOOK_SECRET"] is configured. See Webhook Configuration for the full status-code matrix.

URL Pattern:

from django_cloudflareimages_toolkit.views import WebhookView

path('webhook/', WebhookView.as_view(), name='webhook')

Webhook Events:

  • upload.complete: Image upload and processing completed

  • upload.failed: Image upload failed

  • image.deleted: Image was deleted

Example:

# urls.py
from django.urls import path, include

urlpatterns = [
    path('cloudflare-images/', include('django_cloudflareimages_toolkit.urls')),
]

Management Commands

cleanup_expired_images Command

class django_cloudflareimages_toolkit.management.commands.cleanup_expired_images.Command(stdout=None, stderr=None, no_color=False, force_color=False)[source]

Command to clean up expired image upload URLs.

help = 'Clean up expired image upload URLs and mark them as expired'
add_arguments(parser)[source]

Add command arguments.

handle(*args, **options)[source]

Handle the command execution.

Cleans up expired upload URLs and unused images.

Options:

  • --dry-run: Show what would be cleaned up without making changes

  • --delete: Delete old expired images instead of only marking them expired

  • --days (int): Delete images expired for at least this many days (default: 7)

  • --delete-orphans: Delete uploaded images referenced by no content (orphans)

  • --orphan-days (int): Only delete orphans older than this many days (default: 30)

Example:

# Clean up images older than 7 days
python manage.py cleanup_expired_images --days 7

# Dry run to see what would be deleted
python manage.py cleanup_expired_images --dry-run

# Delete orphaned (unreferenced) images older than 30 days
python manage.py cleanup_expired_images --delete-orphans --orphan-days 30

reconcile_image_usage Command

class django_cloudflareimages_toolkit.management.commands.reconcile_image_usage.Command(stdout=None, stderr=None, no_color=False, force_color=False)[source]

Rebuild the image usage registry and report orphans/unregistered refs.

help = 'Rebuild the image usage registry from host models and report orphans.'
add_arguments(parser)[source]

Entry point for subclassed commands to add custom arguments.

handle(*args, **options)[source]

The actual logic of the command. Subclasses must implement this method.

Rebuilds the image usage registry from host models — the fix for bulk operations that bypass the sync signals — and reports orphans and unregistered references. Idempotent and safe to schedule.

Options:

  • --dry-run: Report what would change without writing.

python manage.py reconcile_image_usage
python manage.py reconcile_image_usage --dry-run

App Configuration

class django_cloudflareimages_toolkit.apps.CloudflareImagesConfig(app_name, app_module)[source]

Bases: AppConfig

App configuration for django_cloudflareimages_toolkit.

default_auto_field = 'django.db.models.BigAutoField'
name = 'django_cloudflareimages_toolkit'
verbose_name = 'Cloudflare Images Toolkit'
ready()[source]

Wire the image-usage registry when Django starts.

Discover every model that declares a CloudflareImageField and connect usage-sync signals to those senders only (keeping the receivers off untracked models). dispatch_uid makes the wiring itself idempotent.

Django app configuration class.

Attributes:

  • default_auto_field: Specifies BigAutoField as default primary key

  • name: App name for Django’s app registry

  • verbose_name: Human-readable app name

Image URL Factory Class

class django_cloudflareimages_toolkit.url_factory.CloudflareImageURLFactory(settings: CloudflareImagesSettings | None = None)[source]

Bases: object

Single source of truth for Cloudflare Images delivery URLs.

The factory builds delivery URLs from their components, recognizes whether an arbitrary URL is a delivery URL, extracts the image id from one, and rewrites Cloudflare’s imagedelivery.net URLs into the configured shape.

All behavior is driven by the DELIVERY_URL, DELIVERY_PATH_PREFIX, and DELIVERY_INCLUDE_ACCOUNT_HASH settings. When DELIVERY_URL is not configured, every method preserves the historical imagedelivery.net behavior.

DEFAULT_HOST = 'imagedelivery.net'
DEFAULT_SCHEME = 'https'
__init__(settings: CloudflareImagesSettings | None = None) None[source]

Initialize the factory.

Parameters:

settings – A settings object exposing delivery_url, delivery_path_prefix, delivery_include_account_hash, and account_hash. Defaults to the global cloudflare_settings singleton.

property uses_custom_domain: bool

Whether an alternate DELIVERY_URL is configured.

property path_prefix: str

Normalized path prefix for custom-domain URLs ("" when default).

property include_account_hash: bool

Whether the account hash appears in the path of built URLs.

base_url(account_hash: str | None = None) str[source]

Return the delivery URL prefix up to (but excluding) the image id.

Parameters:

account_hash – Override for the account hash. Defaults to cloudflare_settings.account_hash when the configured shape includes the hash.

build_url(image_id: str, variant: str = 'public', account_hash: str | None = None) str[source]

Build a full delivery URL honoring the configured shape.

Parameters:
  • image_id – The Cloudflare image id (may contain / for custom paths).

  • variant – The variant name or flexible-variant options. Pass an empty value to omit the trailing segment entirely. Note that a multi-segment image_id combined with an empty variant does not round-trip through extract_image_id(), which treats the last segment as the variant; pair custom-path ids with a variant.

  • account_hash – Optional account-hash override.

Returns:

The fully-qualified delivery URL.

is_delivery_url(url: str) bool[source]

Return True if url is a Cloudflare Images delivery URL.

Recognizes the shared imagedelivery.net host and the configured custom domain (when DELIVERY_URL is set). The host must match exactly — a URL that merely contains imagedelivery.net elsewhere in the path does not qualify — and for a custom domain with a path prefix the URL must use that prefix.

extract_image_id(url: str) str | None[source]

Extract the image id from a delivery URL, or None.

Handles the shared host, the native custom-domain prefix, and the Worker shape, accounting for whether the account hash is present. Multi-segment (custom-path) image ids are preserved.

Note

The final path segment is assumed to be the variant. A custom-path image id used without a variant (e.g. a URL built via build_url("folder/sub/abc", variant="")) cannot be distinguished from “id + variant” and will lose its last segment here. Pair custom-path ids with a variant for reliable round-tripping.

split_variant(url: str) tuple[str, str | None][source]

Split a delivery URL into (base_without_last_segment, last_segment).

Useful for swapping a named variant for flexible-variant options. The query string and fragment are dropped from the returned base.

with_options(url: str, options: str) str[source]

Return a flexible-variant URL applying options to url.

When the delivery URL carries a variant segment, that segment is replaced by options. When it has no variant (e.g. a no-variant custom URL), the options are appended so the image id is preserved rather than overwritten. Query string and fragment are preserved.

rewrite_url(url: str) str[source]

Rewrite a Cloudflare imagedelivery.net URL into the configured shape.

Cloudflare always returns variant URLs on the shared imagedelivery.net host; this rewrites them to the configured custom domain. The query string and fragment (e.g. signed-URL parameters) are preserved.

Returns the URL unchanged when no DELIVERY_URL is configured or when url is not a shared-host delivery URL.

Single source of truth for building, recognizing, extracting from, and rewriting Cloudflare Images delivery URLs. The image_url_factory singleton is exported from the package root. See Image URL Factory for a usage guide and the DELIVERY_URL configuration.

Exception Classes

CloudflareImagesAPIError

exception django_cloudflareimages_toolkit.exceptions.CloudflareImagesAPIError(message: str, status_code: int | None = None, response_data: dict | None = None)[source]

Exception raised when the Cloudflare Images API request fails.

This exception is raised when: - API returns an error status code - Network request fails - Invalid API response format - Authentication failures

Raised when Cloudflare Images API requests fail.

Common Causes:

  • Network connectivity issues

  • Invalid API credentials

  • Rate limiting

  • Server errors

  • Invalid image data

Example:

try:
    service = CloudflareImagesService()
    upload_data = service.get_direct_upload_url()
except CloudflareImagesAPIError as e:
    print(f"API error: {e}")

ConfigurationError

exception django_cloudflareimages_toolkit.exceptions.ConfigurationError(message: str, status_code: int | None = None, response_data: dict | None = None)[source]

Exception raised when configuration is missing or invalid.

This exception is raised when: - Required settings are missing (CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN) - Invalid configuration values - Missing environment variables

Raised when configuration is missing or invalid.

Common Causes:

  • Missing required settings

  • Invalid account credentials

  • Malformed configuration data

Example:

try:
    service = CloudflareImagesService()
except ConfigurationError as e:
    print(f"Configuration error: {e}")

ValidationError

exception django_cloudflareimages_toolkit.exceptions.ValidationError(message: str, status_code: int | None = None, response_data: dict | None = None)[source]

Exception raised when validation fails.

This exception is raised when: - Invalid file types or sizes - Invalid metadata format - Field validation errors - Image processing validation failures

Raised when image data validation fails.

Common Causes:

  • Invalid image format

  • File size exceeds limits

  • Missing required metadata

Example:

try:
    field = CloudflareImageField(allowed_formats=['jpeg'])
    # Validation occurs during model save
except ValidationError as e:
    print(f"Validation error: {e}")

ImageNotFoundError

exception django_cloudflareimages_toolkit.exceptions.ImageNotFoundError(message: str, status_code: int | None = None, response_data: dict | None = None)[source]

Exception raised when an image is not found.

This exception is raised when: - Image ID doesn’t exist in Cloudflare - Image has been deleted - Access denied to image

Raised when an image does not exist in Cloudflare. This is raised by get_image() on a Cloudflare 404 and by CloudflareImage.objects.register_uploaded() when the supplied cloudflare_id does not exist. Importable from both django_cloudflareimages_toolkit and django_cloudflareimages_toolkit.exceptions.

Example:

from django_cloudflareimages_toolkit import ImageNotFoundError

try:
    service.get_image('missing-id')
except ImageNotFoundError as e:
    print(f"Image not found: {e}")

ImageNotReadyError

exception django_cloudflareimages_toolkit.exceptions.ImageNotReadyError(message: str, status_code: int | None = None, response_data: dict | None = None)[source]

Exception raised when an image exists but is not ready to be registered.

This is raised when the image is still a draft – the upload URL was created but no bytes have been uploaded yet, so Cloudflare still reports draft as True. It signals that the cloudflare_id is real but the upload has not completed, so it must not be trusted as finished.

Subclass of CloudflareImagesError. Raised by CloudflareImage.objects.register_uploaded() when the image exists in Cloudflare but is still a draft (the upload has not completed). No local row is created in this case. Importable from both django_cloudflareimages_toolkit and django_cloudflareimages_toolkit.exceptions.

Example:

from django_cloudflareimages_toolkit import ImageNotReadyError

try:
    CloudflareImage.objects.register_uploaded(cloudflare_id)
except ImageNotReadyError as e:
    print(f"Upload not completed yet: {e}")

ImageOwnershipError

exception django_cloudflareimages_toolkit.exceptions.ImageOwnershipError(message: str, status_code: int | None = None, response_data: dict | None = None)[source]

Exception raised when a registered image’s creator does not match.

This is raised by register_uploaded when an expected_creator is supplied and the Cloudflare creator on the image does not equal it. It guards against a caller registering another user’s completed image by submitting an arbitrary cloudflare_id from the same account.

Subclass of CloudflareImagesError. Raised by CloudflareImage.objects.register_uploaded() when an expected_creator is supplied and the Cloudflare creator on the image does not match it. The check runs before any local row is created, so it prevents a caller from registering another user’s completed image. Importable from both django_cloudflareimages_toolkit and django_cloudflareimages_toolkit.exceptions.

Example:

from django_cloudflareimages_toolkit import ImageOwnershipError

try:
    CloudflareImage.objects.register_uploaded(
        cloudflare_id, user=request.user, expected_creator=str(request.user.pk)
    )
except ImageOwnershipError as e:
    print(f"Not your image: {e}")

Utility APIs

There is no separate utils module. The utility-style operations live as methods on the service singleton and the CloudflareImage model, plus the standalone CloudflareImageTransform class for URL construction.

Image URL Generation

URL generation is a method on the model, not a freestanding function. Use get_url() for the default variant or get_variant_url() to select one.

image = CloudflareImage.objects.get(cloudflare_id='abc123')
url = image.get_variant_url('thumbnail')

For transformation URLs (resize, crop, format, watermark) without a CloudflareImage instance, use the standalone builder CloudflareImageTransform:

from django_cloudflareimages_toolkit.transformations import (
    CloudflareImageTransform,
)

# Pass an existing delivery URL to the constructor, chain the
# transforms, then call .build().
url = (
    CloudflareImageTransform("https://imagedelivery.net/your-hash/abc123/public")
    .width(400)
    .height(300)
    .format("auto")
    .quality(85)
    .build()
)

See Patterns & Recipes for a watermarking recipe based on Cloudflare dashboard variants. CloudflareImageTransform builds resize/format URLs; it does not expose Cloudflare’s draw overlay parameter.

Signed URL Generation

Signed URLs are produced by get_signed_url() when require_signed_urls=True on the model row:

image = CloudflareImage.objects.get(cloudflare_id='abc123')
signed_url = image.get_signed_url(variant="public", expiry=3600)

Returns None if the image isn’t uploaded yet or signed URLs are disabled for that row.

Configuration Helpers

Read settings via the cloudflare_settings proxy instead of indexing settings.CLOUDFLARE_IMAGES directly. The proxy validates required keys (ACCOUNT_ID, API_TOKEN) on access and exposes documented defaults for everything else.

from django_cloudflareimages_toolkit.settings import cloudflare_settings

print(cloudflare_settings.account_id)
print(cloudflare_settings.default_expiry_minutes)
secret = cloudflare_settings.webhook_secret  # may be None

Webhook Signature Verification

Signature verification is wired into WebhookView and runs automatically when a secret is configured. If you need to verify a signature outside the bundled view (e.g. inside a Celery task or a custom endpoint), call the service directly:

from django_cloudflareimages_toolkit.services import cloudflare_service

ok = cloudflare_service.validate_webhook_signature(
    request.body, signature_header
)

Returns True only when the HMAC matches. Callers that need strict enforcement should check the secret first or use WebhookView, which encodes the right policy.

Constants and Settings

Default Settings:

# CLOUDFLARE_IMAGES keys and their defaults. Set these under
# settings.CLOUDFLARE_IMAGES; ACCOUNT_ID, ACCOUNT_HASH, and API_TOKEN are required.
CLOUDFLARE_IMAGES_DEFAULTS = {
    'BASE_URL': 'https://api.cloudflare.com/client/v4',
    'DEFAULT_EXPIRY_MINUTES': 30,        # 2-360
    'REQUIRE_SIGNED_URLS': True,
    'DEFAULT_METADATA': {},
    'DEFAULT_CREATOR': None,
    'METADATA_FACTORY': None,
    'WEBHOOK_SECRET': None,
    'MAX_FILE_SIZE_MB': 10,
    'DELIVERY_URL': None,                          # e.g. 'images.example.com'
    'DELIVERY_PATH_PREFIX': 'cdn-cgi/imagedelivery',
    'DELIVERY_INCLUDE_ACCOUNT_HASH': True,
}

API Endpoints:

CLOUDFLARE_IMAGES_API_BASE = 'https://api.cloudflare.com/client/v4'
CLOUDFLARE_IMAGES_DELIVERY_BASE = 'https://imagedelivery.net'

Migration Support

The package includes Django migrations for database schema management:

Initial Migration (0001_initial.py):

  • Creates CloudflareImage model table

  • Sets up indexes for performance

  • Creates constraints for data integrity

Migration Commands:

# Apply migrations
python manage.py migrate django_cloudflareimages_toolkit

# Create new migration (if you modify models)
python manage.py makemigrations django_cloudflareimages_toolkit

Testing Utilities

Mock Service

For testing purposes, you can mock the CloudflareImagesService:

from unittest.mock import patch, MagicMock
from django.test import TestCase

class MyTestCase(TestCase):
    @patch('django_cloudflareimages_toolkit.services.CloudflareImagesService')
    def test_image_upload(self, mock_service):
        mock_instance = MagicMock()
        mock_service.return_value = mock_instance
        mock_instance.get_direct_upload_url.return_value = {
            'id': 'test-id',
            'uploadURL': 'https://test-upload-url.com'
        }

        # Your test code here

Test Image Factory

Create test images for testing:

from django_cloudflareimages_toolkit.models import CloudflareImage

def create_test_image(**kwargs):
    defaults = {
        'cloudflare_id': 'test-image-id',
        'filename': 'test.jpg',
        'is_ready': True,
        'file_size': 1024,
        'width': 800,
        'height': 600,
        'format': 'jpeg'
    }
    defaults.update(kwargs)
    return CloudflareImage.objects.create(**defaults)

Version Information

django_cloudflareimages_toolkit.__version__ = '1.1.4'

str(object=’’) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to ‘strict’.

Current package version string.

Example:

import django_cloudflareimages_toolkit
print(f"Package version: {django_cloudflareimages_toolkit.__version__}")

Logging

The package uses Python’s standard logging module with the logger name django_cloudflareimages_toolkit.

Log Levels:

  • DEBUG: Detailed API request/response information

  • INFO: Successful operations and image processing updates

  • WARNING: Recoverable errors and fallback usage

  • ERROR: Failed operations and API errors

  • CRITICAL: System-level failures

Example Configuration:

import logging

# Configure logging for the package
logging.getLogger('django_cloudflareimages_toolkit').setLevel(logging.INFO)

# Example log output
logger = logging.getLogger('django_cloudflareimages_toolkit')
logger.info("Image uploaded successfully: %s", image_id)

Type Hints

The package includes comprehensive type hints for better IDE support and type checking:

from typing import Dict, List, Optional, Union
from django_cloudflareimages_toolkit.services import CloudflareImagesService

service: CloudflareImagesService = CloudflareImagesService()
upload_data: Dict[str, str] = service.get_direct_upload_url()
images: Dict[str, Union[List, Dict]] = service.list_images()