Source code for imgproxykit.integrations.drf.serializers
from collections.abc import Mapping
from typing import Any, override
from django.db.models.fields.files import FieldFile
from rest_framework import serializers
from imgproxykit.integrations.django import imgproxy_url, imgproxy_variants
from imgproxykit.transform import Transform
[docs]
class ImgproxyURLField(serializers.URLField):
"""
Represent a source string or model file as a read-only imgproxy URL.
Use Django's ``IMGPROXYKIT`` settings without request context. Empty
sources return ``None`` without calling the source resolver.
``read_only`` must be ``True`` and ``write_only`` must be ``False``.
Args:
transform: Transformation or name from ``IMGPROXYKIT['transforms']``.
Omit to add no processing options.
**kwargs: DRF field options. ``allow_null`` defaults to ``True``.
"""
def __init__(
self,
*,
transform: Transform | str | None = None,
**kwargs: Any,
) -> None:
"""Configure the transformation and DRF field options."""
# Remove these options before forwarding kwargs: their values are fixed
# below, and passing them twice would raise TypeError.
if kwargs.pop('read_only', True) is not True:
raise ValueError('read_only must be True for ImgproxyURLField.')
if kwargs.pop('write_only', False) is not False:
raise ValueError('write_only must be False for ImgproxyURLField.')
kwargs.setdefault('allow_null', True)
super().__init__(read_only=True, write_only=False, **kwargs)
self.transform = transform
# Empty sources produce None, which URLField's return type excludes.
@override
def to_representation( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] # ty: ignore[invalid-method-override]
self,
value: str | FieldFile | None,
) -> str | None:
"""Build an imgproxy URL, or return None for an empty source."""
if not value:
return None
return imgproxy_url(value, self.transform)
[docs]
class ImgproxyVariantsField(serializers.DictField):
"""
Represent a source as a read-only dictionary of named imgproxy URLs.
Use Django's ``IMGPROXYKIT`` settings and resolve the source once for
all variants. Empty sources return ``None`` without calling the resolver.
``read_only`` must be ``True`` and ``write_only`` must be ``False``.
``child`` cannot be supplied; variant values always use a URL field.
Args:
variants: Transformation mapping or set name from
``IMGPROXYKIT['variants']``.
**kwargs: DRF field options. ``allow_null`` defaults to ``True``.
"""
def __init__(
self,
*,
variants: Mapping[str, Transform] | str,
**kwargs: Any,
) -> None:
"""Configure the variants and DRF field options."""
# Remove these options before forwarding kwargs: their values are fixed
# below, and passing them twice would raise TypeError.
if kwargs.pop('read_only', True) is not True:
raise ValueError(
'read_only must be True for ImgproxyVariantsField.',
)
if kwargs.pop('write_only', False) is not False:
raise ValueError(
'write_only must be False for ImgproxyVariantsField.',
)
if 'child' in kwargs:
raise TypeError(
'child cannot be supplied for ImgproxyVariantsField.',
)
kwargs.setdefault('allow_null', True)
super().__init__( # pyright: ignore[reportUnknownMemberType]
child=serializers.URLField(),
read_only=True,
write_only=False,
**kwargs,
)
self.variants = variants
# DictField's types exclude source strings, model files, and None output.
@override
def to_representation( # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] # ty: ignore[invalid-method-override]
self,
value: str | FieldFile | None, # type: ignore[override]
) -> dict[str, str] | None:
"""Build named imgproxy URLs, or return None for an empty source."""
if not value:
return None
return imgproxy_variants(value, self.variants)