Coverage for api/views/image_views.py: 85%

57 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 06:14 +0000

1import io 

2 

3from django.conf import settings 

4from django.shortcuts import aget_object_or_404 

5from rest_framework.decorators import action 

6from rest_framework.response import Response 

7 

8from drf_spectacular.utils import extend_schema, extend_schema_view 

9from PIL import Image as PILImage 

10 

11from api.constants.media_types import IMAGE_TYPE 

12from api.docs.image_docs import ( 

13 detail, 

14 oembed, 

15 related, 

16 report, 

17 search, 

18 stats, 

19) 

20from api.docs.image_docs import thumbnail as thumbnail_docs 

21from api.models import Image 

22from api.serializers.image_serializers import ( 

23 ImageReportRequestSerializer, 

24 ImageSearchRequestSerializer, 

25 ImageSerializer, 

26 OembedRequestSerializer, 

27 OembedSerializer, 

28) 

29from api.utils import image_proxy 

30from api.utils.aiohttp import get_aiohttp_session 

31from api.views.media_views import MediaViewSet 

32 

33 

34@extend_schema(tags=["images"]) 

35@extend_schema_view( 

36 list=search, 

37 stats=stats, 

38 retrieve=detail, 

39 related=related, 

40) 

41class ImageViewSet(MediaViewSet): 

42 """Viewset for all endpoints pertaining to images.""" 

43 

44 model_class = Image 

45 media_type = IMAGE_TYPE 

46 query_serializer_class = ImageSearchRequestSerializer 

47 default_index = settings.MEDIA_INDEX_MAPPING[IMAGE_TYPE] 

48 

49 serializer_class = ImageSerializer 

50 

51 OEMBED_HEADERS = { 

52 "User-Agent": settings.OUTBOUND_USER_AGENT_TEMPLATE.format(purpose="OEmbed"), 

53 } 

54 

55 def get_queryset(self): 

56 return super().get_queryset().select_related("sensitive_image") 

57 

58 # Extra actions 

59 

60 @oembed 

61 @action( 

62 detail=False, 

63 url_path="oembed", 

64 url_name="oembed", 

65 serializer_class=OembedSerializer, 

66 ) 

67 async def oembed(self, request, *_, **__): 

68 """ 

69 Retrieve the structured data for a specified image URL as per the 

70 [oEmbed spec](https://oembed.com/). 

71 

72 This info can be used to embed the image on the consumer's website. Only 

73 JSON format is supported. 

74 """ 

75 

76 params = OembedRequestSerializer(data=request.query_params) 

77 params.is_valid(raise_exception=True) 

78 identifier = params.validated_data["identifier"] 

79 context = self.get_serializer_context() 

80 

81 image = await aget_object_or_404(Image, identifier=identifier) 

82 

83 if not (image.height and image.width): 83 ↛ 84line 83 didn't jump to line 84 because the condition on line 83 was never true

84 session = await get_aiohttp_session() 

85 

86 async with session.get( 

87 image.url, headers=self.OEMBED_HEADERS 

88 ) as image_file: 

89 image_content = await image_file.content.read() 

90 

91 with PILImage.open(io.BytesIO(image_content)) as image_file: 

92 width, height = image_file.size 

93 

94 context |= { 

95 "width": width, 

96 "height": height, 

97 } 

98 

99 serializer = self.get_serializer(image, context=context) 

100 return Response(data=await serializer.adata) 

101 

102 async def get_image_proxy_media_info(self) -> image_proxy.MediaInfo: 

103 image = await self.aget_object() 

104 image_url = image.url 

105 # Hotfix to use thumbnails for SMK images 

106 # TODO: Remove when small thumbnail issues are resolved 

107 if "iip.smk.dk" in image_url and image.thumbnail: 107 ↛ 108line 107 didn't jump to line 108 because the condition on line 107 was never true

108 image_url = image.thumbnail 

109 

110 return image_proxy.MediaInfo( 

111 media_identifier=image.identifier, 

112 media_provider=image.provider, 

113 image_url=image_url, 

114 width=image.width, 

115 ) 

116 

117 @thumbnail_docs 

118 @MediaViewSet.thumbnail_action 

119 async def thumbnail(self, request, identifier): 

120 """Retrieve the scaled down and compressed thumbnail of the image.""" 

121 return await super().thumbnail(request) 

122 

123 @report 

124 @action( 

125 detail=True, 

126 methods=["post"], 

127 serializer_class=ImageReportRequestSerializer, 

128 ) 

129 def report(self, request, identifier): 

130 """ 

131 Report an issue about a specified image to Openverse. 

132 

133 By using this endpoint, you can report an image if it infringes 

134 copyright, contains mature or sensitive content or some other reason. 

135 """ 

136 

137 return super().report(request, identifier)