fixed implementation of UGC content uploader
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
"""Shared file-persistence helpers for UGC uploads.
|
||||
|
||||
Stored under MEDIA_ROOT/project_<pk>/... mirroring the avatar layout
|
||||
(avatars/user_<id>/picture.jpeg). Draft uploads live under
|
||||
MEDIA_ROOT/uploads/user_<id>/ until a project adopts them. All files are
|
||||
indexed in FileIndex and served only through the gated file_request endpoint.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.files.storage import default_storage
|
||||
|
||||
from .models import FileIndex
|
||||
|
||||
|
||||
def _compute_md5_size(uploaded):
|
||||
md5 = hashlib.md5()
|
||||
size = 0
|
||||
uploaded.seek(0)
|
||||
while True:
|
||||
chunk = uploaded.read(1024 * 1024)
|
||||
if not chunk:
|
||||
break
|
||||
md5.update(chunk)
|
||||
size += len(chunk)
|
||||
uploaded.seek(0)
|
||||
return md5.hexdigest(), size
|
||||
|
||||
|
||||
def store_file(owner, kind, uploaded, project_pk, subdir='', original_filename=''):
|
||||
"""Persist an uploaded file and index it.
|
||||
|
||||
Returns the FileIndex row. ``subdir`` is e.g. 'versions' or 'gallery'.
|
||||
"""
|
||||
name = getattr(uploaded, 'name', '') or original_filename or 'file'
|
||||
content_type = getattr(uploaded, 'content_type', '') or 'application/octet-stream'
|
||||
md5, size = _compute_md5_size(uploaded)
|
||||
|
||||
ext = os.path.splitext(name)[1] or ''
|
||||
if subdir:
|
||||
stored_path = f'project_{project_pk}/{subdir}/{uuid.uuid4().hex}{ext}'
|
||||
else:
|
||||
stored_path = f'project_{project_pk}/{uuid.uuid4().hex}{ext}'
|
||||
|
||||
actual_path = default_storage.save(stored_path, ContentFile(uploaded.read()))
|
||||
return FileIndex.objects.create(
|
||||
owner=owner,
|
||||
kind=kind,
|
||||
stored_path=actual_path,
|
||||
original_filename=name,
|
||||
content_type=content_type,
|
||||
size=size,
|
||||
md5=md5,
|
||||
)
|
||||
|
||||
|
||||
def store_temp_file(owner, kind, uploaded):
|
||||
"""Persist a draft upload under uploads/user_<id>/ and return its FileIndex."""
|
||||
name = getattr(uploaded, 'name', '') or 'file'
|
||||
content_type = getattr(uploaded, 'content_type', '') or 'application/octet-stream'
|
||||
md5, size = _compute_md5_size(uploaded)
|
||||
|
||||
ext = os.path.splitext(name)[1] or ''
|
||||
stored_path = f'uploads/user_{owner.pk}/{uuid.uuid4().hex}{ext}'
|
||||
actual_path = default_storage.save(stored_path, ContentFile(uploaded.read()))
|
||||
return FileIndex.objects.create(
|
||||
owner=owner,
|
||||
kind=f'temp_{kind}',
|
||||
stored_path=actual_path,
|
||||
original_filename=name,
|
||||
content_type=content_type,
|
||||
size=size,
|
||||
md5=md5,
|
||||
)
|
||||
|
||||
|
||||
def move_file_index(file_index, new_stored_path, kind=None):
|
||||
"""Move an indexed file to a new stored path (chunked copy + delete old),
|
||||
updating the index. Used when a project adopts a temp upload."""
|
||||
if file_index.stored_path and file_index.stored_path != new_stored_path:
|
||||
with default_storage.open(file_index.stored_path, 'rb') as src:
|
||||
actual_path = default_storage.save(new_stored_path, src)
|
||||
if actual_path != file_index.stored_path and default_storage.exists(file_index.stored_path):
|
||||
default_storage.delete(file_index.stored_path)
|
||||
file_index.stored_path = actual_path
|
||||
if kind:
|
||||
file_index.kind = kind
|
||||
file_index.save(update_fields=['stored_path'] if not kind else ['stored_path', 'kind'])
|
||||
return file_index
|
||||
|
||||
|
||||
def delete_file_index(file_index):
|
||||
"""Remove an indexed file from disk and DB (safe no-op when None)."""
|
||||
if file_index is None:
|
||||
return
|
||||
if file_index.stored_path and default_storage.exists(file_index.stored_path):
|
||||
default_storage.delete(file_index.stored_path)
|
||||
file_index.delete()
|
||||
Reference in New Issue
Block a user