Coverage for api/serializers/image_serializers.py: 100%
60 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 06:14 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 06:14 +0000
1from typing import Literal
2from uuid import UUID
4from rest_framework import serializers
6from adrf.serializers import Serializer
8from api.constants.field_order import field_position_map
9from api.constants.field_values import ASPECT_RATIOS, IMAGE_CATEGORIES, IMAGE_SIZES
10from api.models import Image, ImageReport
11from api.serializers.base import BaseModelSerializer
12from api.serializers.fields import EnumCharField
13from api.serializers.media_serializers import (
14 MediaReportRequestSerializer,
15 MediaSearchRequestSerializer,
16 MediaSerializer,
17 get_hyperlinks_serializer,
18)
21#######################
22# Request serializers #
23#######################
26class ImageSearchRequestSerializer(MediaSearchRequestSerializer):
27 """Parse and validate search query string parameters."""
29 field_names = [
30 *MediaSearchRequestSerializer.field_names,
31 "category",
32 "aspect_ratio",
33 "size",
34 ]
35 """
36 Keep the fields names in sync with the actual fields below as this list is
37 used to generate Swagger documentation.
38 """
40 # Ref: api/search/constants/field_values.py
41 category = EnumCharField(
42 plural="categories",
43 enum_class=IMAGE_CATEGORIES,
44 required=False,
45 )
46 aspect_ratio = EnumCharField(
47 plural="aspect ratios",
48 enum_class=ASPECT_RATIOS,
49 required=False,
50 )
51 size = EnumCharField(
52 plural="image sizes",
53 enum_class=IMAGE_SIZES,
54 required=False,
55 )
58class ImageReportRequestSerializer(MediaReportRequestSerializer):
59 identifier = serializers.SlugRelatedField(
60 slug_field="identifier",
61 queryset=Image.objects.all(),
62 source="media_obj",
63 )
65 class Meta(MediaReportRequestSerializer.Meta):
66 model = ImageReport
69########################
70# Response serializers #
71########################
73ImageHyperlinksSerializer = get_hyperlinks_serializer("image")
76class ImageSerializer(ImageHyperlinksSerializer, MediaSerializer):
77 """A single image. Used in search results."""
79 class Meta:
80 model = Image
81 fields = sorted( # keep this list ordered logically
82 [
83 *MediaSerializer.Meta.fields,
84 *ImageHyperlinksSerializer.field_names,
85 "height",
86 "width",
87 ],
88 key=lambda val: field_position_map.get(val, 999),
89 )
90 """
91 Keep the fields names in sync with the actual fields below as this list is
92 used to generate Swagger documentation.
93 """
96##########################
97# Additional serializers #
98##########################
101class OembedRequestSerializer(Serializer):
102 """Parse and validate oEmbed parameters."""
104 url = serializers.URLField(
105 allow_blank=False,
106 help_text="The link to an image present in Openverse.",
107 )
109 def to_internal_value(self, data):
110 data = super().to_internal_value(data)
112 url = data["url"]
113 if url.endswith("/"):
114 url = url[:-1]
115 identifier = url.rsplit("/", 1)[1]
117 try:
118 uuid = UUID(identifier)
119 except ValueError:
120 raise serializers.ValidationError(
121 {"Could not parse identifier from URL.": data["url"]}
122 )
124 data["identifier"] = uuid
125 return data
128class OembedSerializer(BaseModelSerializer):
129 """
130 The embedded content from a specified image URL.
132 This is essentially an ``ImageSerializer`` with some changes to match the oEmbed
133 spec: https://oembed.com.
134 """
136 version = serializers.SerializerMethodField(
137 help_text="The oEmbed version number, always set to 1.0.",
138 )
139 type = serializers.SerializerMethodField(
140 help_text="The resource type, always set to 'photo' for images.",
141 )
143 width = serializers.SerializerMethodField(
144 help_text="The width of the image in pixels."
145 )
146 height = serializers.SerializerMethodField(
147 help_text="The height of the image in pixels."
148 )
149 author_name = serializers.CharField(
150 help_text="The name of the media creator.", # ``copied from ``Image``
151 source="creator",
152 )
153 author_url = serializers.URLField(
154 help_text="A direct link to the media creator.", # copied from ``Image``
155 source="creator_url",
156 )
158 @staticmethod
159 def get_type(*_) -> Literal["photo"]:
160 return "photo"
162 @staticmethod
163 def get_version(*_) -> Literal["1.0"]:
164 return "1.0"
166 class Meta:
167 model = Image
168 fields = [
169 "version",
170 "type",
171 "width",
172 "height",
173 "title",
174 "author_name",
175 "author_url",
176 "license_url",
177 ]
179 def get_width(self, obj) -> int:
180 return self.context.get("width", obj.width)
182 def get_height(self, obj) -> int:
183 return self.context.get("height", obj.height)