Some bugs (box-management didn't work); Tags now on search and in box content
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
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
This commit is contained in:
@@ -18,7 +18,7 @@ spec:
|
|||||||
fsGroupChangePolicy: "OnRootMismatch"
|
fsGroupChangePolicy: "OnRootMismatch"
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: loader
|
- name: loader
|
||||||
image: git.baumann.gr/adebaumann/labhelper-data-loader:0.013
|
image: git.baumann.gr/adebaumann/labhelper-data-loader:0.014
|
||||||
securityContext:
|
securityContext:
|
||||||
runAsUser: 0
|
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" ]
|
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
|
mountPath: /data
|
||||||
containers:
|
containers:
|
||||||
- name: web
|
- name: web
|
||||||
image: git.baumann.gr/adebaumann/labhelper:0.048
|
image: git.baumann.gr/adebaumann/labhelper:0.049
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8000
|
- containerPort: 8000
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
<tr style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
<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;">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;">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>
|
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Description</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -55,6 +56,15 @@
|
|||||||
<td style="padding: 15px 20px;">
|
<td style="padding: 15px 20px;">
|
||||||
<a href="{% url 'thing_detail' thing.id %}" style="color: #667eea; text-decoration: none; font-weight: 500;">{{ thing.name }}</a>
|
<a href="{% url 'thing_detail' thing.id %}" style="color: #667eea; text-decoration: none; font-weight: 500;">{{ thing.name }}</a>
|
||||||
</td>
|
</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>
|
<td style="padding: 15px 20px; color: #777;">{{ thing.description|default:"-" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
<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;">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;">Box</th>
|
||||||
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Description</th>
|
<th style="padding: 15px 20px; text-align: left; font-weight: 600;">Description</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -85,9 +85,16 @@ function performSearch(query) {
|
|||||||
const row = document.createElement('tr');
|
const row = document.createElement('tr');
|
||||||
row.style.borderBottom = '1px solid #e0e0e0';
|
row.style.borderBottom = '1px solid #e0e0e0';
|
||||||
row.style.transition = 'background 0.2s';
|
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 =
|
row.innerHTML =
|
||||||
'<td style="padding: 15px 20px;"><a href="/thing/' + thing.id + '/">' + escapeHtml(thing.name) + '</a></td>' +
|
'<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;"><a href="/box/' + escapeHtml(thing.box) + '/">' + escapeHtml(thing.box) + '</a></td>' +
|
||||||
'<td style="padding: 15px 20px; color: #777;" class="description">' + escapeHtml(thing.description) + '</td>';
|
'<td style="padding: 15px 20px; color: #777;" class="description">' + escapeHtml(thing.description) + '</td>';
|
||||||
|
|
||||||
|
|||||||
@@ -706,12 +706,12 @@ class SearchApiTests(AuthTestCase):
|
|||||||
results = response.json()['results']
|
results = response.json()['results']
|
||||||
self.assertEqual(len(results), 50)
|
self.assertEqual(len(results), 50)
|
||||||
|
|
||||||
def test_search_api_includes_type_and_box(self):
|
def test_search_api_includes_tags_and_box(self):
|
||||||
"""Search API results should include type and box info."""
|
"""Search API results should include tags and box info."""
|
||||||
response = self.client.get('/search/api/?q=ard')
|
response = self.client.get('/search/api/?q=ard')
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
results = response.json()['results']
|
results = response.json()['results']
|
||||||
self.assertEqual(results[0]['type'], 'Electronics')
|
self.assertEqual(results[0]['tags'], [])
|
||||||
self.assertEqual(results[0]['box'], 'BOX001')
|
self.assertEqual(results[0]['box'], 'BOX001')
|
||||||
|
|
||||||
def test_search_api_requires_login(self):
|
def test_search_api_requires_login(self):
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ def index(request):
|
|||||||
def box_detail(request, box_id):
|
def box_detail(request, box_id):
|
||||||
"""Display contents of a box."""
|
"""Display contents of a box."""
|
||||||
box = get_object_or_404(Box, pk=box_id)
|
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', {
|
return render(request, 'boxes/box_detail.html', {
|
||||||
'box': box,
|
'box': box,
|
||||||
'things': things,
|
'things': things,
|
||||||
@@ -179,7 +179,7 @@ def search_api(request):
|
|||||||
things = Thing.objects.filter(
|
things = Thing.objects.filter(
|
||||||
Q(tags__facet__name__icontains=facet_name) &
|
Q(tags__facet__name__icontains=facet_name) &
|
||||||
Q(tags__name__icontains=tag_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:
|
else:
|
||||||
# Normal search
|
# Normal search
|
||||||
things = Thing.objects.filter(
|
things = Thing.objects.filter(
|
||||||
@@ -191,7 +191,7 @@ def search_api(request):
|
|||||||
Q(links__url__icontains=query) |
|
Q(links__url__icontains=query) |
|
||||||
Q(tags__name__icontains=query) |
|
Q(tags__name__icontains=query) |
|
||||||
Q(tags__facet__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 = [
|
results = [
|
||||||
{
|
{
|
||||||
@@ -199,6 +199,13 @@ def search_api(request):
|
|||||||
'name': thing.name,
|
'name': thing.name,
|
||||||
'box': thing.box.id,
|
'box': thing.box.id,
|
||||||
'description': thing.description[:100] if thing.description else '',
|
'description': thing.description[:100] if thing.description else '',
|
||||||
|
'tags': [
|
||||||
|
{
|
||||||
|
'name': tag.name,
|
||||||
|
'color': tag.facet.color,
|
||||||
|
}
|
||||||
|
for tag in thing.tags.all()
|
||||||
|
],
|
||||||
'files': [
|
'files': [
|
||||||
{
|
{
|
||||||
'title': f.title,
|
'title': f.title,
|
||||||
|
|||||||
Binary file not shown.
@@ -22,11 +22,14 @@ from django.contrib.auth import views as auth_views
|
|||||||
|
|
||||||
from boxes.views import (
|
from boxes.views import (
|
||||||
add_box,
|
add_box,
|
||||||
|
add_box_type,
|
||||||
add_things,
|
add_things,
|
||||||
box_detail,
|
box_detail,
|
||||||
box_management,
|
box_management,
|
||||||
delete_box,
|
delete_box,
|
||||||
|
delete_box_type,
|
||||||
edit_box,
|
edit_box,
|
||||||
|
edit_box_type,
|
||||||
index,
|
index,
|
||||||
search,
|
search,
|
||||||
search_api,
|
search_api,
|
||||||
@@ -38,6 +41,9 @@ urlpatterns = [
|
|||||||
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
|
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
|
||||||
path('', index, name='index'),
|
path('', index, name='index'),
|
||||||
path('box-management/', box_management, name='box_management'),
|
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/add/', add_box, name='add_box'),
|
||||||
path('box/<str:box_id>/edit/', edit_box, name='edit_box'),
|
path('box/<str:box_id>/edit/', edit_box, name='edit_box'),
|
||||||
path('box/<str:box_id>/delete/', delete_box, name='delete_box'),
|
path('box/<str:box_id>/delete/', delete_box, name='delete_box'),
|
||||||
|
|||||||
Reference in New Issue
Block a user