@ -263,6 +263,83 @@ class FileFieldSerializer(serializers.CharField):
return File ( open ( fpath , ' rb ' ) , os . path . basename ( data ) )
return File ( open ( fpath , ' rb ' ) , os . path . basename ( data ) )
class UploadChatMediaSerializer ( serializers . Serializer ) :
""" Upload files permanently to /media/chat/ """
file = serializers . FileField ( )
url = serializers . URLField ( read_only = True )
name = serializers . CharField ( read_only = True )
size = serializers . CharField ( read_only = True )
mime_type = serializers . CharField ( read_only = True )
thumbnail_url = serializers . URLField ( read_only = True , required = False )
def to_representation ( self , instance ) :
data = super ( UploadChatMediaSerializer , self ) . to_representation ( instance )
data [ ' file ' ] = instance [ ' file ' ]
return data
def store_file ( self , file ) :
from django.conf import settings
from utils.image_utils import (
create_thumbnail ,
is_image_file ,
is_video_file ,
extract_video_thumbnail
)
media_path = settings . MEDIA_ROOT
os . makedirs ( f ' {media_path}/chat/uploads ' , exist_ok = True )
fpath = f " /chat/uploads/{secrets.token_urlsafe(4)}-{file.name} "
full_path = media_path + fpath
shutil . move ( file . temporary_file_path ( ) , full_path )
os . chmod ( full_path , 0 o644 )
result = {
' file ' : fpath ,
' url ' : absolute_url ( self . context [ ' request ' ] , f " /media{fpath} " ) ,
' name ' : file . name ,
' size ' : sizeof_fmt ( file . size ) ,
' mime_type ' : guess_file_type ( fpath )
}
# Generate thumbnail if file is an image (low quality for preview)
if is_image_file ( full_path ) :
try :
thumbnail_path = create_thumbnail ( full_path , size = ( 200 , 200 ) , quality = 60 )
thumbnail_relative = thumbnail_path . replace ( media_path , ' ' )
result [ ' thumbnail_url ' ] = absolute_url (
self . context [ ' request ' ] ,
f " /media{thumbnail_relative} "
)
except Exception as e :
print ( f " Failed to generate image thumbnail: {e} " )
result [ ' thumbnail_url ' ] = None
# Generate thumbnail if file is a video (low quality for preview)
elif is_video_file ( full_path ) :
try :
thumbnail_path = extract_video_thumbnail (
full_path ,
time_offset = ' 00:00:01 ' ,
size = ( 200 , 200 ) ,
quality = 60
)
thumbnail_relative = thumbnail_path . replace ( media_path , ' ' )
result [ ' thumbnail_url ' ] = absolute_url (
self . context [ ' request ' ] ,
f " /media{thumbnail_relative} "
)
except Exception as e :
print ( f " Failed to generate video thumbnail: {e} " )
result [ ' thumbnail_url ' ] = None
else :
result [ ' thumbnail_url ' ] = None
return result
def validate ( self , attrs ) :
file_details = self . store_file ( attrs [ ' file ' ] )
return file_details
class UploadTmpSerializer ( serializers . Serializer ) :
class UploadTmpSerializer ( serializers . Serializer ) :
file = serializers . FileField ( )
file = serializers . FileField ( )
url = serializers . URLField ( read_only = True )
url = serializers . URLField ( read_only = True )
@ -300,37 +377,33 @@ class UploadTmpSerializer(serializers.Serializer):
' mime_type ' : guess_file_type ( fpath )
' mime_type ' : guess_file_type ( fpath )
}
}
# Generate thumbnail if file is an image
# Generate thumbnail if file is an image (low quality for preview)
if is_image_file ( full_path ) :
if is_image_file ( full_path ) :
try :
try :
thumbnail_path = create_thumbnail ( full_path , size = ( 300 , 300 ) , quality = 85 )
# Convert absolute path to relative URL path
thumbnail_path = create_thumbnail ( full_path , size = ( 200 , 200 ) , quality = 60 )
thumbnail_relative = thumbnail_path . replace ( static_path , ' ' )
thumbnail_relative = thumbnail_path . replace ( static_path , ' ' )
result [ ' thumbnail_url ' ] = absolute_url (
result [ ' thumbnail_url ' ] = absolute_url (
self . context [ ' request ' ] ,
self . context [ ' request ' ] ,
f " /static{thumbnail_relative} "
f " /static{thumbnail_relative} "
)
)
except Exception as e :
except Exception as e :
# Log error but don't fail the upload
print ( f " Failed to generate image thumbnail: {e} " )
print ( f " Failed to generate image thumbnail: {e} " )
result [ ' thumbnail_url ' ] = None
result [ ' thumbnail_url ' ] = None
# Generate thumbnail if file is a video
# Generate thumbnail if file is a video (low quality for preview)
elif is_video_file ( full_path ) :
elif is_video_file ( full_path ) :
try :
try :
thumbnail_path = extract_video_thumbnail (
thumbnail_path = extract_video_thumbnail (
full_path ,
full_path ,
time_offset = ' 00:00:01 ' , # Extract frame at 1 second
size = ( 300 , 3 00) ,
quality = 85
time_offset = ' 00:00:01 ' ,
size = ( 200 , 2 00) ,
quality = 60
)
)
# Convert absolute path to relative URL path
thumbnail_relative = thumbnail_path . replace ( static_path , ' ' )
thumbnail_relative = thumbnail_path . replace ( static_path , ' ' )
result [ ' thumbnail_url ' ] = absolute_url (
result [ ' thumbnail_url ' ] = absolute_url (
self . context [ ' request ' ] ,
self . context [ ' request ' ] ,
f " /static{thumbnail_relative} "
f " /static{thumbnail_relative} "
)
)
except Exception as e :
except Exception as e :
# Log error but don't fail the upload
print ( f " Failed to generate video thumbnail: {e} " )
print ( f " Failed to generate video thumbnail: {e} " )
result [ ' thumbnail_url ' ] = None
result [ ' thumbnail_url ' ] = None
else :
else :
@ -343,9 +416,27 @@ class UploadTmpSerializer(serializers.Serializer):
return file_details
return file_details
class UploadChatMedia ( GenericAPIView ) :
"""
Upload files permanently to / media / chat /
Files are stored permanently and will NOT be removed
"""
parser_classes = ( FormParser , MultiPartParser )
serializer_class = UploadChatMediaSerializer
def post ( self , request : HttpRequest , * args , * * kwargs ) :
serializer = UploadChatMediaSerializer ( data = request . FILES , context = { ' request ' : request } )
is_valid = serializer . is_valid ( raise_exception = True )
if not is_valid :
return Response ( serializer . errors , status = status . HTTP_400_BAD_REQUEST )
return Response ( serializer . data )
class UploadTmpMedia ( GenericAPIView ) :
class UploadTmpMedia ( GenericAPIView ) :
"""
"""
Files will remove every 1 hour
Upload files temporarily to / static / tmp /
Files will be removed every 1 hour
"""
"""
parser_classes = ( FormParser , MultiPartParser )
parser_classes = ( FormParser , MultiPartParser )
serializer_class = UploadTmpSerializer
serializer_class = UploadTmpSerializer