working auto-tags on implemented UGC Categories

This commit is contained in:
JakeBreath
2026-08-05 10:54:29 -05:00
parent 358d29f9c9
commit f08c2c37dd
3 changed files with 134 additions and 14 deletions
@@ -0,0 +1,18 @@
# Generated by Django 6.0.3 on 2026-08-05 15:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0011_version_mod_manifest'),
]
operations = [
migrations.AddField(
model_name='taglist',
name='auto',
field=models.BooleanField(default=False),
),
]
+1
View File
@@ -189,6 +189,7 @@ class TagList(models.Model):
added_by = models.ForeignKey( added_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, related_name='+', settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, related_name='+',
) )
auto = models.BooleanField(default=False)
added_at = models.DateTimeField(auto_now_add=True) added_at = models.DateTimeField(auto_now_add=True)
class Meta: class Meta:
+115 -14
View File
@@ -39,11 +39,12 @@ from .zips import (
read_animation_manifest, read_animation_manifest,
read_logical_manifest, read_logical_manifest,
read_models_manifest, read_models_manifest,
read_mod_version_manifest,
read_pack_meta, read_pack_meta,
read_pack_mcmeta, read_pack_mcmeta,
read_plugins_manifest, read_plugins_manifest,
read_skins_manifest, read_skins_manifest,
read_mod_version_manifest, _mc_from_filename,
) )
@@ -543,19 +544,120 @@ ANIMATION_API_WARNING = (
) )
def _apply_content_tags(project, tag_names, actor): def _clean_mc_version(value):
"""Auto-create a content:<tag> for every distinct animation content tag.""" """Extract the first X.Y from an MC version/range ('>=26.2 <26.3' -> '26.2')."""
content_cat = TagCategory.objects.filter(slug='content').first() m = re.search(r'(\d+\.\d+)', str(value or ''))
if content_cat is None: return m.group(1) if m else ''
return
for name in tag_names:
def _vanilla_entities():
"""Set of real Minecraft entity names from the bundled vanilla index.
Excludes renderer aliases (slime_size_*, player_slim) that are model
variants rather than Minecraft entities.
"""
aliases = {'slime_size_0', 'slime_size_1', 'slime_size_3', 'player_slim'}
try:
with open(settings.BASE_DIR / 'static' / 'vanilla' / 'entity_index.json') as f:
return set(json.load(f).keys()) - aliases
except (OSError, ValueError):
return set()
def _species_entity(entity):
"""Map model-variant names to their real Minecraft entity."""
return {
'player_slim': 'player',
'slime_size_0': 'slime',
'slime_size_1': 'slime',
'slime_size_3': 'slime',
}.get(entity, entity)
def _auto_tag_specs(project, version):
"""Deduped list of (category_slug, tag_name) auto-tags for a version."""
specs = []
def add(cat, name):
name = slugify_tag(name) name = slugify_tag(name)
if not name: if name and (cat, name) not in specs:
specs.append((cat, name))
if project.category == 'mod':
for m in (version.mod_manifest or {}).get('mods', []):
if m.get('modloader'):
add('loader', m['modloader'])
mc = _clean_mc_version(m.get('minecraft'))
if not mc:
mc = _mc_from_filename(m.get('file_name') or '')
if mc:
add('version', mc)
elif project.category == 'modpack':
for loader in (version.mods_manifest or {}).get('modloaders', []):
add('loader', loader)
mc = (version.mods_manifest or {}).get('minecraft')
if mc:
add('version', mc)
elif project.category == 'model':
vanilla = _vanilla_entities()
for m in (version.models_manifest or {}).get('models', []):
ext = (m.get('ext') or '').lstrip('.')
if ext == 'geo.json':
ext = 'geo'
if ext:
add('format', ext)
entity = _species_entity(m.get('entity') or '')
if entity and entity in vanilla:
add('species', entity)
elif project.category == 'skin':
add('general', 'skin')
elif project.category == 'guide':
add('general', 'guide')
elif project.category == 'non_pack':
# NoN packs may carry custom/modded species — no vanilla whitelist.
# Only skip placeholder model names (pregnancy-egg stubs, etc.).
placeholders = {'default', 'minecraft_spider'}
for tag in (version.animation_manifest or {}).get('content_tags', []):
add('content', tag)
for m in (version.models_manifest or {}).get('models', []):
entity = _species_entity(m.get('entity') or '')
if entity and entity not in placeholders:
add('species', entity)
return specs
def _sync_auto_tags(project, actor=None, version=None):
"""Apply auto tags (loader/version/format/species/general/content) from the
given version's manifests. Marks auto links, cleans stale ones, and never
touches manual links (arbitrary tags like creator:LiZo are preserved)."""
actor = actor or project.owner
version = version or project.versions.first()
if version is None:
return
TagCategory.objects.get_or_create(slug='format', defaults={'name': 'Format', 'color': '#0e7490'})
specs = _auto_tag_specs(project, version)
spec_set = set(specs)
cats = {}
for cat_slug, name in specs:
cat = cats.get(cat_slug) or TagCategory.objects.filter(slug=cat_slug).first()
if cat is None:
continue continue
cats[cat_slug] = cat
tag, _ = Tag.objects.get_or_create( tag, _ = Tag.objects.get_or_create(
name=name, category=content_cat, defaults={'created_by': actor}, name=name, category=cat, defaults={'created_by': actor},
) )
TagList.objects.get_or_create(project=project, tag=tag, defaults={'added_by': actor}) link, created = TagList.objects.get_or_create(
project=project, tag=tag, defaults={'added_by': actor, 'auto': True},
)
if not created and not link.auto:
link.auto = True
link.save(update_fields=['auto'])
for link in project.tag_links.select_related('tag__category').filter(auto=True).all():
if (link.tag.category.slug, link.tag.name) not in spec_set:
link.delete()
def _established_animation_id(project): def _established_animation_id(project):
@@ -689,6 +791,7 @@ def _reparse_version(version):
version.pack_format = fmt version.pack_format = fmt
version.pack_description = desc version.pack_description = desc
version.save() version.save()
_sync_auto_tags(version.project, version.project.owner, version.project.versions.first())
def _finalize_version(project, version_name, changelog, temp_uploads, actor): def _finalize_version(project, version_name, changelog, temp_uploads, actor):
@@ -792,10 +895,7 @@ def _finalize_version(project, version_name, changelog, temp_uploads, actor):
if update_fields: if update_fields:
version.save(update_fields=update_fields) version.save(update_fields=update_fields)
if animation_manifest is not None: _sync_auto_tags(project, actor, version)
_apply_content_tags(
project, animation_manifest.get('content_tags') or [], actor,
)
return version, missing_animation_id return version, missing_animation_id
@@ -912,6 +1012,7 @@ def project_edit(request, slug):
project.save() project.save()
_apply_tags(project, data['tags'], request.user) _apply_tags(project, data['tags'], request.user)
project.sync_creator_tags(actor=request.user) project.sync_creator_tags(actor=request.user)
_sync_auto_tags(project, request.user)
messages.success(request, 'Project updated.') messages.success(request, 'Project updated.')
return redirect('library:project_detail', slug=project.slug) return redirect('library:project_detail', slug=project.slug)
else: else: