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

1from typing import Literal 

2from uuid import UUID 

3 

4from rest_framework import serializers 

5 

6from adrf.serializers import Serializer 

7 

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) 

19 

20 

21####################### 

22# Request serializers # 

23####################### 

24 

25 

26class ImageSearchRequestSerializer(MediaSearchRequestSerializer): 

27 """Parse and validate search query string parameters.""" 

28 

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 """ 

39 

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 ) 

56 

57 

58class ImageReportRequestSerializer(MediaReportRequestSerializer): 

59 identifier = serializers.SlugRelatedField( 

60 slug_field="identifier", 

61 queryset=Image.objects.all(), 

62 source="media_obj", 

63 ) 

64 

65 class Meta(MediaReportRequestSerializer.Meta): 

66 model = ImageReport 

67 

68 

69######################## 

70# Response serializers # 

71######################## 

72 

73ImageHyperlinksSerializer = get_hyperlinks_serializer("image") 

74 

75 

76class ImageSerializer(ImageHyperlinksSerializer, MediaSerializer): 

77 """A single image. Used in search results.""" 

78 

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 """ 

94 

95 

96########################## 

97# Additional serializers # 

98########################## 

99 

100 

101class OembedRequestSerializer(Serializer): 

102 """Parse and validate oEmbed parameters.""" 

103 

104 url = serializers.URLField( 

105 allow_blank=False, 

106 help_text="The link to an image present in Openverse.", 

107 ) 

108 

109 def to_internal_value(self, data): 

110 data = super().to_internal_value(data) 

111 

112 url = data["url"] 

113 if url.endswith("/"): 

114 url = url[:-1] 

115 identifier = url.rsplit("/", 1)[1] 

116 

117 try: 

118 uuid = UUID(identifier) 

119 except ValueError: 

120 raise serializers.ValidationError( 

121 {"Could not parse identifier from URL.": data["url"]} 

122 ) 

123 

124 data["identifier"] = uuid 

125 return data 

126 

127 

128class OembedSerializer(BaseModelSerializer): 

129 """ 

130 The embedded content from a specified image URL. 

131 

132 This is essentially an ``ImageSerializer`` with some changes to match the oEmbed 

133 spec: https://oembed.com. 

134 """ 

135 

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 ) 

142 

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 ) 

157 

158 @staticmethod 

159 def get_type(*_) -> Literal["photo"]: 

160 return "photo" 

161 

162 @staticmethod 

163 def get_version(*_) -> Literal["1.0"]: 

164 return "1.0" 

165 

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 ] 

178 

179 def get_width(self, obj) -> int: 

180 return self.context.get("width", obj.width) 

181 

182 def get_height(self, obj) -> int: 

183 return self.context.get("height", obj.height)