Complete replacement of Thing types with tag system

This commit is contained in:
2026-01-03 22:23:35 +01:00
parent cb3e9d6aec
commit cd04a21157
15 changed files with 530 additions and 195 deletions

View File

@@ -11,25 +11,28 @@ from .forms import (
ThingLinkForm,
ThingPictureForm,
)
from .models import Box, BoxType, Thing, ThingFile, ThingLink, ThingType
from .models import Box, BoxType, Facet, Tag, Thing, ThingFile, ThingLink
@login_required
def index(request):
"""Home page with boxes and thing types."""
"""Home page with boxes and tags."""
boxes = Box.objects.select_related('box_type').all().order_by('id')
thing_types = ThingType.objects.all()
type_counts = {}
for thing_type in thing_types:
descendants = thing_type.get_descendants(include_self=True)
count = Thing.objects.filter(thing_type__in=descendants).count()
type_counts[thing_type.pk] = count
facets = Facet.objects.all().prefetch_related('tags')
facet_tag_counts = {}
for facet in facets:
for tag in facet.tags.all():
count = tag.things.count()
if count > 0:
if facet not in facet_tag_counts:
facet_tag_counts[facet] = []
facet_tag_counts[facet].append((tag, count))
return render(request, 'boxes/index.html', {
'boxes': boxes,
'thing_types': thing_types,
'type_counts': type_counts,
'facets': facets,
'facet_tag_counts': facet_tag_counts,
})
@@ -37,7 +40,7 @@ def index(request):
def box_detail(request, box_id):
"""Display contents of a box."""
box = get_object_or_404(Box, pk=box_id)
things = box.things.select_related('thing_type').all()
things = box.things.all()
return render(request, 'boxes/box_detail.html', {
'box': box,
'things': things,
@@ -48,11 +51,12 @@ def box_detail(request, box_id):
def thing_detail(request, thing_id):
"""Display details of a thing."""
thing = get_object_or_404(
Thing.objects.select_related('thing_type', 'box', 'box__box_type').prefetch_related('files', 'links'),
Thing.objects.select_related('box', 'box__box_type').prefetch_related('files', 'links', 'tags'),
pk=thing_id
)
boxes = Box.objects.select_related('box_type').all().order_by('id')
facets = Facet.objects.all().prefetch_related('tags')
picture_form = ThingPictureForm(instance=thing)
file_form = ThingFileForm()
link_form = ThingLinkForm()
@@ -118,9 +122,34 @@ def thing_detail(request, thing_id):
pass
return redirect('thing_detail', thing_id=thing.id)
elif action == 'add_tag':
tag_id = request.POST.get('tag_id')
if tag_id:
try:
tag = Tag.objects.get(pk=tag_id)
if tag.facet.cardinality == Facet.Cardinality.SINGLE:
existing_tags = list(thing.tags.filter(facet=tag.facet))
for existing_tag in existing_tags:
thing.tags.remove(existing_tag)
thing.tags.add(tag)
except Tag.DoesNotExist:
pass
return redirect('thing_detail', thing_id=thing.id)
elif action == 'remove_tag':
tag_id = request.POST.get('tag_id')
if tag_id:
try:
tag = Tag.objects.get(pk=tag_id)
thing.tags.remove(tag)
except Tag.DoesNotExist:
pass
return redirect('thing_detail', thing_id=thing.id)
return render(request, 'boxes/thing_detail.html', {
'thing': thing,
'boxes': boxes,
'facets': facets,
'picture_form': picture_form,
'file_form': file_form,
'link_form': link_form,
@@ -140,21 +169,34 @@ def search_api(request):
if len(query) < 2:
return JsonResponse({'results': []})
things = Thing.objects.filter(
Q(name__icontains=query) |
Q(description__icontains=query) |
Q(thing_type__name__icontains=query) |
Q(files__title__icontains=query) |
Q(files__file__icontains=query) |
Q(links__title__icontains=query) |
Q(links__url__icontains=query)
).prefetch_related('files', 'links').select_related('thing_type', 'box').distinct()[:50]
# Check for "Facet:Word" format
if ':' in query:
parts = query.split(':',1)
facet_name = parts[0].strip()
tag_name = parts[1].strip()
# Search for things with specific facet and tag
things = Thing.objects.filter(
Q(tags__facet__name__icontains=facet_name) &
Q(tags__name__icontains=tag_name)
).prefetch_related('files', 'links').select_related('box').distinct()[:50]
else:
# Normal search
things = Thing.objects.filter(
Q(name__icontains=query) |
Q(description__icontains=query) |
Q(files__title__icontains=query) |
Q(files__file__icontains=query) |
Q(links__title__icontains=query) |
Q(links__url__icontains=query) |
Q(tags__name__icontains=query) |
Q(tags__facet__name__icontains=query)
).prefetch_related('files', 'links').select_related('box').distinct()[:50]
results = [
{
'id': thing.id,
'name': thing.name,
'type': thing.thing_type.name,
'box': thing.box.id,
'description': thing.description[:100] if thing.description else '',
'files': [
@@ -208,25 +250,6 @@ def add_things(request, box_id):
})
@login_required
def thing_type_detail(request, type_id):
"""Display details of a thing type with its hierarchy and things."""
thing_type = get_object_or_404(ThingType, pk=type_id)
descendants = thing_type.get_descendants(include_self=True)
things_by_type = {}
for descendant in descendants:
things = descendant.things.select_related('box', 'box__box_type').all()
if things:
things_by_type[descendant] = things
return render(request, 'boxes/thing_type_detail.html', {
'thing_type': thing_type,
'things_by_type': things_by_type,
})
@login_required
def box_management(request):
"""Main page for managing boxes and box types."""