phase 1 backup - support for multi-file versioned upload
This commit is contained in:
+92
-42
@@ -25,6 +25,7 @@ from .models import (
|
||||
TagList,
|
||||
TempUpload,
|
||||
Version,
|
||||
VersionFile,
|
||||
slugify_tag,
|
||||
)
|
||||
from .storage import delete_file_index, move_file_index, store_temp_file
|
||||
@@ -307,7 +308,7 @@ def project_detail(request, slug):
|
||||
project = get_object_or_404(
|
||||
Project.objects.select_related('owner', 'thumbnail'), slug=slug,
|
||||
)
|
||||
versions = project.versions.select_related('file').all()
|
||||
versions = project.versions.prefetch_related('files__file').all()
|
||||
assets = project.assets.select_related('file').all()
|
||||
tags = project.tag_links.select_related('tag__category').all()
|
||||
can_edit = project.can_edit(request.user)
|
||||
@@ -323,7 +324,7 @@ def project_detail(request, slug):
|
||||
|
||||
DRAFT_KEYS = (
|
||||
'title', 'summary', 'category', 'description',
|
||||
'tags', 'caption', 'version_name', 'changelog',
|
||||
'tags', 'version_name', 'changelog',
|
||||
)
|
||||
|
||||
|
||||
@@ -344,6 +345,13 @@ def _pending_uploads(user):
|
||||
)
|
||||
|
||||
|
||||
def _pending_kind(user, kind):
|
||||
return list(
|
||||
TempUpload.objects.filter(user=user, status='pending', kind=kind)
|
||||
.select_related('file').order_by('created_at')
|
||||
)
|
||||
|
||||
|
||||
def _latest_pending(user, kind):
|
||||
return (
|
||||
TempUpload.objects.filter(user=user, status='pending', kind=kind)
|
||||
@@ -358,6 +366,35 @@ def _adopt_temp(upload, project_pk, subdir, kind):
|
||||
return move_file_index(upload.file, new_path, kind=kind)
|
||||
|
||||
|
||||
def _classify_version_file(file_index, category):
|
||||
"""Pick a VersionFile.kind from the uploaded file's extension."""
|
||||
name = (file_index.original_filename or '').lower()
|
||||
if name.endswith('.md'):
|
||||
return 'markdown'
|
||||
if name.endswith('.png'):
|
||||
return 'png'
|
||||
return 'release'
|
||||
|
||||
|
||||
def _finalize_version(project, version_name, changelog, temp_uploads):
|
||||
"""Adopt pending 'version' temp uploads into a new Version as VersionFiles."""
|
||||
version = Version.objects.create(
|
||||
project=project,
|
||||
version_name=version_name,
|
||||
changelog=changelog,
|
||||
)
|
||||
for temp in temp_uploads:
|
||||
index = _adopt_temp(temp, project.pk, 'versions', 'version')
|
||||
VersionFile.objects.create(
|
||||
version=version,
|
||||
file=index,
|
||||
kind=_classify_version_file(index, project.category),
|
||||
)
|
||||
temp.status = 'used'
|
||||
temp.save(update_fields=['status'])
|
||||
return version
|
||||
|
||||
|
||||
def _form_values(*forms):
|
||||
out = {}
|
||||
for form in forms:
|
||||
@@ -376,9 +413,9 @@ def project_create(request):
|
||||
version_form = VersionForm(request.POST)
|
||||
if project_form.is_valid() and version_form.is_valid():
|
||||
data = project_form.cleaned_data
|
||||
version_file = _latest_pending(request.user, 'version')
|
||||
if version_file is None:
|
||||
version_form.add_error('version_name', 'Upload the version file before creating the project.')
|
||||
version_files = _pending_kind(request.user, 'version')
|
||||
if not version_files:
|
||||
version_form.add_error('version_name', 'Upload at least one version file before creating the project.')
|
||||
else:
|
||||
project = Project.objects.create(
|
||||
slug=_unique_project_slug(data['title']),
|
||||
@@ -396,24 +433,21 @@ def project_create(request):
|
||||
thumb.save(update_fields=['status'])
|
||||
project.save(update_fields=['thumbnail'])
|
||||
|
||||
version_index = _adopt_temp(version_file, project.pk, 'versions', 'version')
|
||||
version_file.status = 'used'
|
||||
version_file.save(update_fields=['status'])
|
||||
Version.objects.create(
|
||||
project=project,
|
||||
version_name=version_form.cleaned_data['version_name'],
|
||||
file=version_index,
|
||||
changelog=version_form.cleaned_data['changelog'],
|
||||
_finalize_version(
|
||||
project,
|
||||
version_form.cleaned_data['version_name'],
|
||||
version_form.cleaned_data['changelog'],
|
||||
version_files,
|
||||
)
|
||||
|
||||
caption = data.get('caption', '')
|
||||
for temp in TempUpload.objects.filter(
|
||||
user=request.user, status='pending', kind='media',
|
||||
).select_related('file').order_by('created_at'):
|
||||
index = _adopt_temp(temp, project.pk, 'gallery', 'asset')
|
||||
ProjectAsset.objects.create(
|
||||
project=project, file=index,
|
||||
caption=caption, uploaded_by=request.user,
|
||||
caption=request.POST.get(f'caption_{temp.uuid}', '')[:128],
|
||||
uploaded_by=request.user,
|
||||
)
|
||||
temp.status = 'used'
|
||||
temp.save(update_fields=['status'])
|
||||
@@ -511,7 +545,8 @@ def project_delete(request, slug):
|
||||
indices = []
|
||||
if project.thumbnail_id:
|
||||
indices.append(project.thumbnail)
|
||||
indices += [v.file for v in project.versions.all() if v.file_id]
|
||||
for version in project.versions.prefetch_related('files__file').all():
|
||||
indices += [vf.file for vf in version.files.all() if vf.file_id]
|
||||
indices += [a.file for a in project.assets.all() if a.file_id]
|
||||
project.delete()
|
||||
for index in indices:
|
||||
@@ -531,41 +566,56 @@ def version_upload(request, slug):
|
||||
if request.method == 'POST':
|
||||
form = VersionForm(request.POST)
|
||||
if form.is_valid():
|
||||
version_file = _latest_pending(request.user, 'version')
|
||||
if version_file is None:
|
||||
form.add_error('version_name', 'Upload the version file before uploading.')
|
||||
version_files = _pending_kind(request.user, 'version')
|
||||
if not version_files:
|
||||
form.add_error('version_name', 'Upload at least one version file before uploading.')
|
||||
else:
|
||||
index = _adopt_temp(version_file, project.pk, 'versions', 'version')
|
||||
version_file.status = 'used'
|
||||
version_file.save(update_fields=['status'])
|
||||
Version.objects.create(
|
||||
project=project,
|
||||
version_name=form.cleaned_data['version_name'],
|
||||
file=index,
|
||||
changelog=form.cleaned_data['changelog'],
|
||||
_finalize_version(
|
||||
project,
|
||||
form.cleaned_data['version_name'],
|
||||
form.cleaned_data['changelog'],
|
||||
version_files,
|
||||
)
|
||||
messages.success(request, f'Version {form.cleaned_data["version_name"]} uploaded.')
|
||||
return redirect('library:project_detail', slug=project.slug)
|
||||
|
||||
version_file = _latest_pending(request.user, 'version')
|
||||
version_files = _pending_kind(request.user, 'version')
|
||||
return render(request, 'library/version_upload.html', {
|
||||
'project': project,
|
||||
'version_form': form,
|
||||
'pending_uploads': [_serialize_temp(version_file)] if version_file else [],
|
||||
'pending_uploads': [_serialize_temp(u) for u in version_files],
|
||||
})
|
||||
|
||||
|
||||
def _bump_and_redirect(request, version, file_index):
|
||||
Version.objects.filter(pk=version.pk).update(downloads=F('downloads') + 1)
|
||||
return redirect(
|
||||
reverse('library:file_request', args=[file_index.uuid]) + '?download=1'
|
||||
)
|
||||
|
||||
|
||||
def version_download(request, slug, version_id):
|
||||
"""Download the first release file of a version (kept for API/mods)."""
|
||||
version = get_object_or_404(
|
||||
Version.objects.select_related('file', 'project'),
|
||||
Version.objects.select_related('project'),
|
||||
pk=version_id, project__slug=slug,
|
||||
)
|
||||
if version.file_id:
|
||||
Version.objects.filter(pk=version.pk).update(downloads=F('downloads') + 1)
|
||||
return redirect(
|
||||
reverse('library:file_request', args=[version.file.uuid]) + '?download=1'
|
||||
)
|
||||
raise Http404
|
||||
first = version.files.select_related('file').first()
|
||||
if first is None:
|
||||
raise Http404
|
||||
return _bump_and_redirect(request, version, first.file)
|
||||
|
||||
|
||||
def version_file_download(request, slug, version_id, file_uuid):
|
||||
"""Download a specific file of a version."""
|
||||
version = get_object_or_404(
|
||||
Version.objects.select_related('project'),
|
||||
pk=version_id, project__slug=slug,
|
||||
)
|
||||
vf = get_object_or_404(
|
||||
version.files.select_related('file'), file__uuid=file_uuid,
|
||||
)
|
||||
return _bump_and_redirect(request, version, vf.file)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -575,7 +625,6 @@ def asset_upload(request, slug):
|
||||
return HttpResponseForbidden('You do not have permission to add media.')
|
||||
|
||||
if request.method == 'POST':
|
||||
caption = request.POST.get('caption', '')
|
||||
media = TempUpload.objects.filter(user=request.user, status='pending', kind='media')
|
||||
if not media.exists():
|
||||
messages.error(request, 'No media uploaded yet.')
|
||||
@@ -585,7 +634,8 @@ def asset_upload(request, slug):
|
||||
index = _adopt_temp(temp, project.pk, 'gallery', 'asset')
|
||||
ProjectAsset.objects.create(
|
||||
project=project, file=index,
|
||||
caption=caption, uploaded_by=request.user,
|
||||
caption=request.POST.get(f'caption_{temp.uuid}', '')[:128],
|
||||
uploaded_by=request.user,
|
||||
)
|
||||
temp.status = 'used'
|
||||
temp.save(update_fields=['status'])
|
||||
@@ -595,7 +645,6 @@ def asset_upload(request, slug):
|
||||
|
||||
return render(request, 'library/asset_upload.html', {
|
||||
'project': project,
|
||||
'caption': request.POST.get('caption', ''),
|
||||
'pending_uploads': [_serialize_temp(u) for u in _pending_uploads(request.user)],
|
||||
})
|
||||
|
||||
@@ -707,9 +756,10 @@ def api_upload_temp(request):
|
||||
if kind == 'thumbnail' and not content_type.startswith('image/'):
|
||||
return JsonResponse({'error': 'The thumbnail must be an image.'}, status=400)
|
||||
|
||||
# Thumbnail and version are single slots: discard any earlier pending one.
|
||||
if kind in ('thumbnail', 'version'):
|
||||
for old in TempUpload.objects.filter(user=request.user, status='pending', kind=kind):
|
||||
# Thumbnail is a single slot: discard any earlier pending one. Version and
|
||||
# media accept multiple files (one Version can hold several files).
|
||||
if kind == 'thumbnail':
|
||||
for old in TempUpload.objects.filter(user=request.user, status='pending', kind='thumbnail'):
|
||||
delete_file_index(old.file)
|
||||
old.delete()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user