All checks were successful
Build containers when image tags change / build-if-image-changed (., web, containers, main container, git.baumann.gr/adebaumann/labhelper) (push) Successful in 1m3s
Build containers when image tags change / build-if-image-changed (data-loader, loader, initContainers, init-container, git.baumann.gr/adebaumann/labhelper-data-loader) (push) Successful in 12s
338 lines
11 KiB
Python
338 lines
11 KiB
Python
from django.contrib.auth.decorators import login_required
|
|
from django.db.models import Q
|
|
from django.http import HttpResponse, JsonResponse
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
|
|
from .forms import (
|
|
BoxForm,
|
|
BoxTypeForm,
|
|
ThingFileForm,
|
|
ThingFormSet,
|
|
ThingLinkForm,
|
|
ThingPictureForm,
|
|
)
|
|
from .models import Box, BoxType, Facet, Tag, Thing, ThingFile, ThingLink
|
|
|
|
|
|
@login_required
|
|
def index(request):
|
|
"""Home page with boxes and tags."""
|
|
boxes = Box.objects.select_related('box_type').all().order_by('id')
|
|
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,
|
|
'facets': facets,
|
|
'facet_tag_counts': facet_tag_counts,
|
|
})
|
|
|
|
|
|
@login_required
|
|
def box_detail(request, box_id):
|
|
"""Display contents of a box."""
|
|
box = get_object_or_404(Box, pk=box_id)
|
|
things = box.things.prefetch_related('tags').all()
|
|
return render(request, 'boxes/box_detail.html', {
|
|
'box': box,
|
|
'things': things,
|
|
})
|
|
|
|
|
|
@login_required
|
|
def thing_detail(request, thing_id):
|
|
"""Display details of a thing."""
|
|
thing = get_object_or_404(
|
|
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()
|
|
|
|
if request.method == 'POST':
|
|
action = request.POST.get('action')
|
|
|
|
if action == 'move':
|
|
new_box_id = request.POST.get('new_box')
|
|
if new_box_id:
|
|
new_box = get_object_or_404(Box, pk=new_box_id)
|
|
thing.box = new_box
|
|
thing.save()
|
|
return redirect('thing_detail', thing_id=thing.id)
|
|
|
|
elif action == 'upload_picture':
|
|
picture_form = ThingPictureForm(request.POST, request.FILES, instance=thing)
|
|
if picture_form.is_valid():
|
|
picture_form.save()
|
|
return redirect('thing_detail', thing_id=thing.id)
|
|
|
|
elif action == 'delete_picture':
|
|
if thing.picture:
|
|
thing.picture.delete()
|
|
thing.picture = None
|
|
thing.save()
|
|
return redirect('thing_detail', thing_id=thing.id)
|
|
|
|
elif action == 'add_file':
|
|
file_form = ThingFileForm(request.POST, request.FILES)
|
|
if file_form.is_valid():
|
|
thing_file = file_form.save(commit=False)
|
|
thing_file.thing = thing
|
|
thing_file.save()
|
|
return redirect('thing_detail', thing_id=thing.id)
|
|
|
|
elif action == 'add_link':
|
|
link_form = ThingLinkForm(request.POST)
|
|
if link_form.is_valid():
|
|
thing_link = link_form.save(commit=False)
|
|
thing_link.thing = thing
|
|
thing_link.save()
|
|
return redirect('thing_detail', thing_id=thing.id)
|
|
|
|
elif action == 'delete_file':
|
|
file_id = request.POST.get('file_id')
|
|
if file_id:
|
|
try:
|
|
thing_file = ThingFile.objects.get(pk=file_id, thing=thing)
|
|
thing_file.file.delete()
|
|
thing_file.delete()
|
|
except ThingFile.DoesNotExist:
|
|
pass
|
|
return redirect('thing_detail', thing_id=thing.id)
|
|
|
|
elif action == 'delete_link':
|
|
link_id = request.POST.get('link_id')
|
|
if link_id:
|
|
try:
|
|
thing_link = ThingLink.objects.get(pk=link_id, thing=thing)
|
|
thing_link.delete()
|
|
except ThingLink.DoesNotExist:
|
|
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,
|
|
})
|
|
|
|
|
|
@login_required
|
|
def search(request):
|
|
"""Search page for things."""
|
|
return render(request, 'boxes/search.html')
|
|
|
|
|
|
@login_required
|
|
def search_api(request):
|
|
"""AJAX endpoint for searching things."""
|
|
query = request.GET.get('q', '').strip()
|
|
if len(query) < 2:
|
|
return JsonResponse({'results': []})
|
|
|
|
# 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', 'tags').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', 'tags').select_related('box').distinct()[:50]
|
|
|
|
results = [
|
|
{
|
|
'id': thing.id,
|
|
'name': thing.name,
|
|
'box': thing.box.id,
|
|
'description': thing.description[:100] if thing.description else '',
|
|
'tags': [
|
|
{
|
|
'name': tag.name,
|
|
'color': tag.facet.color,
|
|
}
|
|
for tag in thing.tags.all()
|
|
],
|
|
'files': [
|
|
{
|
|
'title': f.title,
|
|
'filename': f.filename(),
|
|
}
|
|
for f in thing.files.all()
|
|
],
|
|
'links': [
|
|
{
|
|
'title': l.title,
|
|
'url': l.url,
|
|
}
|
|
for l in thing.links.all()
|
|
],
|
|
}
|
|
for thing in things
|
|
]
|
|
return JsonResponse({'results': results})
|
|
|
|
|
|
@login_required
|
|
def add_things(request, box_id):
|
|
"""Add multiple things to a box at once."""
|
|
box = get_object_or_404(Box, pk=box_id)
|
|
|
|
success_message = None
|
|
|
|
if request.method == 'POST':
|
|
formset = ThingFormSet(request.POST, request.FILES, queryset=Thing.objects.filter(box=box))
|
|
|
|
if formset.is_valid():
|
|
things = formset.save(commit=False)
|
|
created_count = 0
|
|
for thing in things:
|
|
if thing.name or thing.thing_type or thing.description or thing.picture:
|
|
thing.box = box
|
|
thing.save()
|
|
created_count += 1
|
|
if created_count > 0:
|
|
success_message = f'Added {created_count} thing{"s" if created_count > 1 else ""} successfully.'
|
|
formset = ThingFormSet(queryset=Thing.objects.filter(box=box))
|
|
else:
|
|
formset = ThingFormSet(queryset=Thing.objects.filter(box=box))
|
|
|
|
return render(request, 'boxes/add_things.html', {
|
|
'box': box,
|
|
'formset': formset,
|
|
'success_message': success_message,
|
|
})
|
|
|
|
|
|
@login_required
|
|
def box_management(request):
|
|
"""Main page for managing boxes and box types."""
|
|
box_types = BoxType.objects.all().prefetch_related('boxes')
|
|
boxes = Box.objects.select_related('box_type').all().prefetch_related('things')
|
|
box_type_form = BoxTypeForm()
|
|
box_form = BoxForm()
|
|
|
|
return render(request, 'boxes/box_management.html', {
|
|
'box_types': box_types,
|
|
'boxes': boxes,
|
|
'box_type_form': box_type_form,
|
|
'box_form': box_form,
|
|
})
|
|
|
|
|
|
@login_required
|
|
def add_box_type(request):
|
|
"""Add a new box type."""
|
|
if request.method == 'POST':
|
|
form = BoxTypeForm(request.POST)
|
|
if form.is_valid():
|
|
form.save()
|
|
return redirect('box_management')
|
|
|
|
|
|
@login_required
|
|
def edit_box_type(request, type_id):
|
|
"""Edit an existing box type."""
|
|
box_type = get_object_or_404(BoxType, pk=type_id)
|
|
if request.method == 'POST':
|
|
form = BoxTypeForm(request.POST, instance=box_type)
|
|
if form.is_valid():
|
|
form.save()
|
|
return redirect('box_management')
|
|
|
|
|
|
@login_required
|
|
def delete_box_type(request, type_id):
|
|
"""Delete a box type."""
|
|
box_type = get_object_or_404(BoxType, pk=type_id)
|
|
if request.method == 'POST':
|
|
if box_type.boxes.exists():
|
|
return redirect('box_management')
|
|
box_type.delete()
|
|
return redirect('box_management')
|
|
|
|
|
|
@login_required
|
|
def add_box(request):
|
|
"""Add a new box."""
|
|
if request.method == 'POST':
|
|
form = BoxForm(request.POST)
|
|
if form.is_valid():
|
|
form.save()
|
|
return redirect('box_management')
|
|
|
|
|
|
@login_required
|
|
def edit_box(request, box_id):
|
|
"""Edit an existing box."""
|
|
box = get_object_or_404(Box, pk=box_id)
|
|
if request.method == 'POST':
|
|
form = BoxForm(request.POST, instance=box)
|
|
if form.is_valid():
|
|
form.save()
|
|
return redirect('box_management')
|
|
|
|
|
|
@login_required
|
|
def delete_box(request, box_id):
|
|
"""Delete a box."""
|
|
box = get_object_or_404(Box, pk=box_id)
|
|
if request.method == 'POST':
|
|
if box.things.exists():
|
|
return redirect('box_management')
|
|
box.delete()
|
|
return redirect('box_management')
|