Browse Source

feat(media): add UploadChatMedia functionality for permanent file storage

- Introduced UploadChatMedia and UploadChatMediaSerializer to handle permanent file uploads to /media/chat/.
- Updated urls.py to include a new endpoint for chat media uploads.
- Enhanced file handling with thumbnail generation for images and videos.
- Adjusted UploadTmpSerializer to maintain existing temporary upload functionality.
master
mortezaei 7 months ago
parent
commit
c6121a271c
  1. 5
      config/urls.py
  2. 113
      utils/__init__.py

5
config/urls.py

@ -20,7 +20,7 @@ from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
from django.conf.urls.i18n import i18n_patterns from django.conf.urls.i18n import i18n_patterns
from django.contrib.admin.views.decorators import staff_member_required from django.contrib.admin.views.decorators import staff_member_required
from utils import UploadTmpMedia
from utils import UploadTmpMedia, UploadChatMedia
from django.http import JsonResponse from django.http import JsonResponse
from django.shortcuts import render from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
@ -87,7 +87,8 @@ api_patterns = [
path('settings/', include('dynamic_preferences.urls')), path('settings/', include('dynamic_preferences.urls')),
path('upload-tmp-media/', UploadTmpMedia.as_view()),
path('upload-chat-media/', UploadChatMedia.as_view()), # دائمی در /media/chat/
path('upload-tmp-media/', UploadTmpMedia.as_view()), # موقت در /static/tmp/
] ]

113
utils/__init__.py

@ -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, 0o644)
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, 300),
quality=85
time_offset='00:00:01',
size=(200, 200),
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

Loading…
Cancel
Save