3
3
Serializers for the content app.
4
4
"""
5
5
6
+ from io import BytesIO
6
7
from typing import Any , Dict , Union
7
8
9
+ from django .conf import settings
10
+ from django .core .files .uploadedfile import InMemoryUploadedFile , UploadedFile
8
11
from django .utils .translation import gettext as _
12
+ from PIL import Image as PILImage
9
13
from rest_framework import serializers
10
14
15
+ from communities .organizations .models import OrganizationImage
11
16
from content .models import (
12
17
Discussion ,
13
18
DiscussionEntry ,
@@ -34,31 +39,86 @@ class Meta:
34
39
fields = "__all__"
35
40
36
41
42
+ # MARK: Clear Metadata
43
+
44
+
45
+ def scrub_exif (image_file : InMemoryUploadedFile ) -> InMemoryUploadedFile :
46
+ """
47
+ Remove EXIF metadata from JPEGs and text metadata from PNGs.
48
+ """
49
+ try :
50
+ img : PILImage .Image = PILImage .open (image_file )
51
+ output_format = img .format
52
+
53
+ if output_format == "JPEG" :
54
+ img = img .convert ("RGB" )
55
+
56
+ elif output_format == "PNG" :
57
+ img = img .copy ()
58
+ img .info = {}
59
+
60
+ else :
61
+ return image_file # return as-is if it's not JPEG or PNG
62
+
63
+ # Save the cleaned image into a buffer.
64
+ output = BytesIO ()
65
+ img .save (
66
+ output ,
67
+ format = output_format ,
68
+ quality = 95 if output_format == "JPEG" else None , # set JPEG quality
69
+ optimize = output_format == "JPEG" , # optimize JPEG
70
+ )
71
+ output .seek (0 )
72
+
73
+ # Return a new InMemoryUploadedFile
74
+ return InMemoryUploadedFile (
75
+ output ,
76
+ image_file .field_name , # use original field name
77
+ image_file .name ,
78
+ f"image/{ output_format .lower ()} " ,
79
+ output .getbuffer ().nbytes ,
80
+ image_file .charset , # preserve charset (if applicable)
81
+ )
82
+
83
+ except Exception as e :
84
+ print (f"Error scrubbing EXIF: { e } " )
85
+ return image_file # return original file in case of error
86
+
87
+
37
88
class ImageSerializer (serializers .ModelSerializer [Image ]):
38
89
class Meta :
39
90
model = Image
40
91
fields = ["id" , "file_object" , "creation_date" ]
41
92
read_only_fields = ["id" , "creation_date" ]
42
93
43
- def validate (self , data : Dict [str , Union [str , int ]]) -> Dict [str , Union [str , int ]]:
44
- # Remove string validation since we're getting a file object.
94
+ def validate (self , data : Dict [str , UploadedFile ]) -> Dict [str , UploadedFile ]:
45
95
if "file_object" not in data :
46
96
raise serializers .ValidationError ("No file was submitted." )
47
97
98
+ # DATA_UPLOAD_MAX_MEMORY_SIZE and IMAGE_UPLOAD_MAX_FILE_SIZE are set in core/settings.py.
99
+ # The file size limit is not being enforced. We're checking the file size here.
100
+ if (
101
+ data ["file_object" ].size is not None
102
+ and data ["file_object" ].size > settings .IMAGE_UPLOAD_MAX_FILE_SIZE
103
+ ):
104
+ raise serializers .ValidationError (
105
+ f"The file size ({ data ['file_object' ].size } bytes) is too large. The maximum file size is { settings .IMAGE_UPLOAD_MAX_FILE_SIZE } bytes."
106
+ )
107
+
48
108
return data
49
109
50
110
# Using 'Any' type until a more correct type is determined.
51
111
def create (self , validated_data : Dict [str , Any ]) -> Image :
52
- if file_obj := self .context ["request" ].FILES .get ("file_object" ):
53
- validated_data ["file_object" ] = file_obj
112
+ request = self .context ["request" ]
54
113
55
- # Create the image first.
56
- image = super (). create ( validated_data )
114
+ if file_obj := request . FILES . get ( "file_object" ):
115
+ validated_data [ "file_object" ] = scrub_exif ( file_obj )
57
116
58
- if organization_id := self .context ["request" ].data .get ("organization_id" ):
59
- # Create OrganizationImage with next sequence index.
60
- from communities .organizations .models import OrganizationImage
117
+ # Create the image instance.
118
+ image = super ().create (validated_data )
61
119
120
+ # Handle organization image indexing if applicable.
121
+ if organization_id := request .data .get ("organization_id" ):
62
122
next_index = OrganizationImage .objects .filter (
63
123
org_id = organization_id
64
124
).count ()
0 commit comments