Some bugs (box-management didn't work); Tags now on search and in box content #5

Merged
adebaumann merged 1 commits from feature/tagging into master 2026-01-04 10:12:25 +00:00
7 changed files with 40 additions and 10 deletions

View File

@@ -18,7 +18,7 @@ spec:
fsGroupChangePolicy: "OnRootMismatch"
initContainers:
- name: loader
image: git.baumann.gr/adebaumann/labhelper-data-loader:0.013
image: git.baumann.gr/adebaumann/labhelper-data-loader:0.014
securityContext:
runAsUser: 0
command: [ "sh","-c","if [ ! -f /data/db.sqlite3 ] || [ ! -s /data/db.sqlite3 ]; then cp preload/preload.sqlite3 /data/db.sqlite3 && echo 'Database copied from preload'; else echo 'Existing database preserved'; fi" ]
@@ -27,7 +27,7 @@ spec:
mountPath: /data
containers:
- name: web
image: git.baumann.gr/adebaumann/labhelper:0.048
image: git.baumann.gr/adebaumann/labhelper:0.049
imagePullPolicy: Always
ports:
- containerPort: 8000

View File

@@ -37,6 +37,7 @@
<tr style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Picture</th>
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Name</th>
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Tags</th>
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Description</th>
</tr>
</thead>
@@ -55,6 +56,15 @@
<td style="padding: 15px 20px;">
<a href="{% url 'thing_detail' thing.id %}" style="color: #667eea; text-decoration: none; font-weight: 500;">{{ thing.name }}</a>
</td>
<td style="padding: 15px 20px;">
{% if thing.tags.all %}
{% for tag in thing.tags.all %}
<span style="display: inline-block; padding: 3px 8px; margin: 2px; border-radius: 12px; font-size: 11px; background: {{ tag.facet.color }}20; color: {{ tag.facet.color }}; border: 1px solid {{ tag.facet.color }}40;">{{ tag.name }}</span>
{% endfor %}
{% else %}
<span style="color: #999; font-style: italic; font-size: 13px;">-</span>
{% endif %}
</td>
<td style="padding: 15px 20px; color: #777;">{{ thing.description|default:"-" }}</td>
</tr>
{% endfor %}

View File

@@ -29,7 +29,7 @@
<thead>
<tr style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Name</th>
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Type</th>
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Tags</th>
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Box</th>
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Description</th>
</tr>
@@ -85,9 +85,16 @@ function performSearch(query) {
const row = document.createElement('tr');
row.style.borderBottom = '1px solid #e0e0e0';
row.style.transition = 'background 0.2s';
let tagsHtml = thing.tags.length > 0
? thing.tags.map(tag =>
'<span style="display: inline-block; padding: 3px 8px; margin: 2px; border-radius: 12px; font-size: 11px; background: ' + escapeHtml(tag.color) + '20; color: ' + escapeHtml(tag.color) + '; border: 1px solid ' + escapeHtml(tag.color) + '40;">' + escapeHtml(tag.name) + '</span>'
).join('')
: '<span style="color: #999; font-style: italic; font-size: 13px;">-</span>';
row.innerHTML =
'<td style="padding: 15px 20px;"><a href="/thing/' + thing.id + '/">' + escapeHtml(thing.name) + '</a></td>' +
'<td style="padding: 15px 20px; color: #555;">' + escapeHtml(thing.type) + '</td>' +
'<td style="padding: 15px 20px;">' + tagsHtml + '</td>' +
'<td style="padding: 15px 20px;"><a href="/box/' + escapeHtml(thing.box) + '/">' + escapeHtml(thing.box) + '</a></td>' +
'<td style="padding: 15px 20px; color: #777;" class="description">' + escapeHtml(thing.description) + '</td>';

View File

@@ -706,12 +706,12 @@ class SearchApiTests(AuthTestCase):
results = response.json()['results']
self.assertEqual(len(results), 50)
def test_search_api_includes_type_and_box(self):
"""Search API results should include type and box info."""
def test_search_api_includes_tags_and_box(self):
"""Search API results should include tags and box info."""
response = self.client.get('/search/api/?q=ard')
self.assertEqual(response.status_code, 200)
results = response.json()['results']
self.assertEqual(results[0]['type'], 'Electronics')
self.assertEqual(results[0]['tags'], [])
self.assertEqual(results[0]['box'], 'BOX001')
def test_search_api_requires_login(self):

View File

@@ -40,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.all()
things = box.things.prefetch_related('tags').all()
return render(request, 'boxes/box_detail.html', {
'box': box,
'things': things,
@@ -179,7 +179,7 @@ def search_api(request):
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]
).prefetch_related('files', 'links', 'tags').select_related('box').distinct()[:50]
else:
# Normal search
things = Thing.objects.filter(
@@ -191,7 +191,7 @@ def search_api(request):
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]
).prefetch_related('files', 'links', 'tags').select_related('box').distinct()[:50]
results = [
{
@@ -199,6 +199,13 @@ def search_api(request):
'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,

Binary file not shown.

View File

@@ -22,11 +22,14 @@ from django.contrib.auth import views as auth_views
from boxes.views import (
add_box,
add_box_type,
add_things,
box_detail,
box_management,
delete_box,
delete_box_type,
edit_box,
edit_box_type,
index,
search,
search_api,
@@ -38,6 +41,9 @@ urlpatterns = [
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
path('', index, name='index'),
path('box-management/', box_management, name='box_management'),
path('box-type/add/', add_box_type, name='add_box_type'),
path('box-type/<int:type_id>/edit/', edit_box_type, name='edit_box_type'),
path('box-type/<int:type_id>/delete/', delete_box_type, name='delete_box_type'),
path('box/add/', add_box, name='add_box'),
path('box/<str:box_id>/edit/', edit_box, name='edit_box'),
path('box/<str:box_id>/delete/', delete_box, name='delete_box'),