backup for Phase 2.5

This commit is contained in:
JakeBreath
2026-08-04 00:34:02 -05:00
parent a81e31bf1a
commit 8c13c5e5ec
9 changed files with 745 additions and 11 deletions
+107 -10
View File
@@ -30,7 +30,7 @@ from .models import (
slugify_tag,
)
from .storage import delete_file_index, move_file_index, store_temp_file
from .zips import parse_mods_manifest, read_pack_mcmeta
from .zips import parse_mods_manifest, read_animation_manifest, read_pack_mcmeta
def _open_indexed_file(file_index):
@@ -316,10 +316,24 @@ def project_detail(request, slug):
can_edit = project.can_edit(request.user)
guide_docs = []
if project.category == 'guide':
latest = versions.first()
if latest is not None:
guide_docs = [vf for vf in latest.files.all() if vf.is_markdown]
animation_manifest = None
anim_stats = None
latest = versions.first()
if project.category == 'guide' and latest is not None:
guide_docs = [vf for vf in latest.files.all() if vf.is_markdown]
if latest is not None and latest.animation_manifest:
animation_manifest = latest.animation_manifest
anims = animation_manifest.get('animations') or []
entity_count = sum(1 for a in anims if a.get('type') == 'Entity x Player')
solo_count = sum(1 for a in anims if a.get('type') == 'Solo')
anim_stats = {
'total': len(anims),
'entity': entity_count,
'player': len(anims) - entity_count - solo_count,
'solo': solo_count,
'flagged': sum(1 for a in anims if a.get('problem_tags')),
'tags': sorted({t for a in anims for t in (a.get('content_tags') or [])}),
}
context = {
'project': project,
@@ -328,10 +342,43 @@ def project_detail(request, slug):
'tags': tags,
'can_edit': can_edit,
'guide_docs': guide_docs,
'animation_manifest': animation_manifest,
'anim_stats': anim_stats,
}
return render(request, 'library/project_detail.html', context)
@login_required
def api_packs_latest(request, namespace, pack_id):
"""Auto-updater endpoint: newest version for a NoN pack id (namespace:pack_id)."""
animation_id = f'{namespace}:{pack_id}'
version = (
Version.objects.filter(animation_id=animation_id)
.select_related('project').prefetch_related('files__file')
.order_by('-created_at').first()
)
if version is None:
return JsonResponse({'detail': 'Not found'}, status=404)
release = next(
(vf for vf in version.files.all() if vf.kind == 'release'),
version.files.first(),
)
if release is None:
return JsonResponse({'detail': 'No file'}, status=404)
download_url = request.build_absolute_uri(
reverse('library:file_request', args=[release.file.uuid]) + '?download=1'
)
return JsonResponse({
'namespace': namespace,
'pack_id': pack_id,
'name': version.project.title,
'version': version.version_name,
'pack_format': version.pack_format,
'created_at': version.created_at.isoformat(),
'download_url': download_url,
})
def guide_doc(request, slug, version_id, file_uuid):
"""Render one markdown guide document server-side (used by the switcher)."""
version = get_object_or_404(
@@ -410,9 +457,34 @@ def _stored_path(file_index):
return Path(settings.MEDIA_ROOT) / file_index.stored_path
def _finalize_version(project, version_name, changelog, temp_uploads):
ANIMATION_API_WARNING = (
'Warning: your pack is incompatible with the update-check API endpoint and '
'may not work with applications that check for updates. Add an '
"'animationframework' block to pack.mcmeta, e.g.: "
'{"pack":{"pack_format":64},"animationframework":{"id":"yourname:yourpack",'
'"name":"Your Pack","author":"You","version":"1.0.0","description":"..."}}'
)
def _apply_content_tags(project, tag_names, actor):
"""Auto-create a content:<tag> for every distinct animation content tag."""
content_cat = TagCategory.objects.filter(slug='content').first()
if content_cat is None:
return
for name in tag_names:
name = slugify_tag(name)
if not name:
continue
tag, _ = Tag.objects.get_or_create(
name=name, category=content_cat, defaults={'created_by': actor},
)
TagList.objects.get_or_create(project=project, tag=tag, defaults={'added_by': actor})
def _finalize_version(project, version_name, changelog, temp_uploads, actor):
"""Adopt pending 'version' temp uploads into a new Version as VersionFiles,
capturing category-specific metadata (pack.mcmeta / mods manifest)."""
capturing category-specific metadata (pack.mcmeta / mods manifest / animation
manifest). Returns (version, missing_animation_id)."""
version = Version.objects.create(
project=project,
version_name=version_name,
@@ -421,6 +493,8 @@ def _finalize_version(project, version_name, changelog, temp_uploads):
pack_format = None
pack_description = ''
mods_manifest = {}
animation_manifest = None
missing_animation_id = False
for temp in temp_uploads:
index = _adopt_temp(temp, project.pk, 'versions', 'version')
@@ -442,6 +516,12 @@ def _finalize_version(project, version_name, changelog, temp_uploads):
manifest = parse_mods_manifest(_stored_path(index), index.original_filename)
if manifest.get('files'):
mods_manifest = manifest
if is_zip and animation_manifest is None:
manifest = read_animation_manifest(_stored_path(index))
if manifest is not None:
animation_manifest = manifest
if not manifest.get('animation_id'):
missing_animation_id = True
update_fields = []
if pack_format is not None:
@@ -451,9 +531,20 @@ def _finalize_version(project, version_name, changelog, temp_uploads):
if mods_manifest:
version.mods_manifest = mods_manifest
update_fields.append('mods_manifest')
if animation_manifest is not None:
version.animation_manifest = animation_manifest
update_fields.append('animation_manifest')
if animation_manifest.get('animation_id'):
version.animation_id = animation_manifest['animation_id']
update_fields.append('animation_id')
if update_fields:
version.save(update_fields=update_fields)
return version
if animation_manifest is not None:
_apply_content_tags(
project, animation_manifest.get('content_tags') or [], actor,
)
return version, missing_animation_id
def _form_values(*forms):
@@ -494,12 +585,15 @@ def project_create(request):
thumb.save(update_fields=['status'])
project.save(update_fields=['thumbnail'])
_finalize_version(
_, missing_animation_id = _finalize_version(
project,
version_form.cleaned_data['version_name'],
version_form.cleaned_data['changelog'],
version_files,
request.user,
)
if missing_animation_id:
messages.warning(request, ANIMATION_API_WARNING)
for temp in TempUpload.objects.filter(
user=request.user, status='pending', kind='media',
@@ -631,12 +725,15 @@ def version_upload(request, slug):
if not version_files:
form.add_error('version_name', 'Upload at least one version file before uploading.')
else:
_finalize_version(
_, missing_animation_id = _finalize_version(
project,
form.cleaned_data['version_name'],
form.cleaned_data['changelog'],
version_files,
request.user,
)
if missing_animation_id:
messages.warning(request, ANIMATION_API_WARNING)
messages.success(request, f'Version {form.cleaned_data["version_name"]} uploaded.')
return redirect('library:project_detail', slug=project.slug)