diff --git a/argocd/deployment.yaml b/argocd/deployment.yaml index d4b7b71..b4bf478 100644 --- a/argocd/deployment.yaml +++ b/argocd/deployment.yaml @@ -18,14 +18,14 @@ spec: fsGroupChangePolicy: "OnRootMismatch" initContainers: - name: loader - image: git.baumann.gr/adebaumann/labhelper-data-loader:0.004 + image: git.baumann.gr/adebaumann/labhelper-data-loader:0.005 command: [ "sh","-c","cp -n preload/preload.sqlite3 /data/db.sqlite3; chown -R 999:999 /data; ls -la /data; sleep 10; exit 0" ] volumeMounts: - name: data mountPath: /data containers: - name: web - image: git.baumann.gr/adebaumann/labhelper:0.010 + image: git.baumann.gr/adebaumann/labhelper:0.011 imagePullPolicy: Always ports: - containerPort: 8000 diff --git a/boxes/templates/boxes/box_detail.html b/boxes/templates/boxes/box_detail.html new file mode 100644 index 0000000..68d3a87 --- /dev/null +++ b/boxes/templates/boxes/box_detail.html @@ -0,0 +1,119 @@ +{% load thumbnail %} + + + + + + Box {{ box.id }} - LabHelper + + + + ← Back to Home + +

Box {{ box.id }}

+ +
+ Type: {{ box.box_type.name }} + ({{ box.box_type.width }} x {{ box.box_type.height }} x {{ box.box_type.length }} mm) +
+ + {% if things %} + + + + + + + + + + + {% for thing in things %} + + + + + + + {% endfor %} + +
PictureNameTypeDescription
+ {% if thing.picture %} + {% thumbnail thing.picture "200x200" crop="center" as thumb %} + {{ thing.name }} + {% endthumbnail %} + {% else %} +
No image
+ {% endif %} +
{{ thing.name }}{{ thing.thing_type.name }}{{ thing.description|default:"-" }}
+ {% else %} +
+ This box is empty. +
+ {% endif %} + + diff --git a/boxes/tests.py b/boxes/tests.py index 35a6207..8433342 100644 --- a/boxes/tests.py +++ b/boxes/tests.py @@ -1,7 +1,8 @@ from django.contrib.admin.sites import AdminSite from django.core.files.uploadedfile import SimpleUploadedFile from django.db import IntegrityError -from django.test import TestCase +from django.test import Client, TestCase +from django.urls import reverse from .admin import BoxAdmin, BoxTypeAdmin, ThingAdmin, ThingTypeAdmin from .models import Box, BoxType, Thing, ThingType @@ -337,3 +338,152 @@ class ThingAdminTests(TestCase): def test_search_fields(self): """ThingAdmin should search by name and description.""" self.assertEqual(self.admin.search_fields, ('name', 'description')) + + +class IndexViewTests(TestCase): + """Tests for the index view.""" + + def setUp(self): + """Set up test client.""" + self.client = Client() + + def test_index_returns_200(self): + """Index page should return 200 status.""" + response = self.client.get('/') + self.assertEqual(response.status_code, 200) + + def test_index_contains_labhelper(self): + """Index page should contain LabHelper title.""" + response = self.client.get('/') + self.assertContains(response, 'LabHelper') + + def test_index_contains_admin_link(self): + """Index page should contain link to admin.""" + response = self.client.get('/') + self.assertContains(response, '/admin/') + + +class BoxDetailViewTests(TestCase): + """Tests for the box detail view.""" + + def setUp(self): + """Set up test fixtures.""" + self.client = Client() + self.box_type = BoxType.objects.create( + name='Standard Box', + width=200, + height=100, + length=300 + ) + self.box = Box.objects.create( + id='BOX001', + box_type=self.box_type + ) + self.thing_type = ThingType.objects.create(name='Electronics') + + def test_box_detail_returns_200(self): + """Box detail page should return 200 status.""" + response = self.client.get(f'/box/{self.box.id}/') + self.assertEqual(response.status_code, 200) + + def test_box_detail_returns_404_for_invalid_box(self): + """Box detail page should return 404 for non-existent box.""" + response = self.client.get('/box/INVALID/') + self.assertEqual(response.status_code, 404) + + def test_box_detail_shows_box_id(self): + """Box detail page should show box ID.""" + response = self.client.get(f'/box/{self.box.id}/') + self.assertContains(response, 'BOX001') + + def test_box_detail_shows_box_type(self): + """Box detail page should show box type name.""" + response = self.client.get(f'/box/{self.box.id}/') + self.assertContains(response, 'Standard Box') + + def test_box_detail_shows_dimensions(self): + """Box detail page should show box dimensions.""" + response = self.client.get(f'/box/{self.box.id}/') + self.assertContains(response, '200') + self.assertContains(response, '100') + self.assertContains(response, '300') + + def test_box_detail_shows_empty_message(self): + """Box detail page should show empty message when no things.""" + response = self.client.get(f'/box/{self.box.id}/') + self.assertContains(response, 'This box is empty') + + def test_box_detail_shows_thing(self): + """Box detail page should show things in the box.""" + Thing.objects.create( + name='Arduino Uno', + thing_type=self.thing_type, + box=self.box, + description='A microcontroller board' + ) + response = self.client.get(f'/box/{self.box.id}/') + self.assertContains(response, 'Arduino Uno') + self.assertContains(response, 'Electronics') + self.assertContains(response, 'A microcontroller board') + + def test_box_detail_shows_no_image_placeholder(self): + """Box detail page should show placeholder for things without images.""" + Thing.objects.create( + name='Test Item', + thing_type=self.thing_type, + box=self.box + ) + response = self.client.get(f'/box/{self.box.id}/') + self.assertContains(response, 'No image') + + def test_box_detail_shows_multiple_things(self): + """Box detail page should show multiple things.""" + Thing.objects.create( + name='Item One', + thing_type=self.thing_type, + box=self.box + ) + Thing.objects.create( + name='Item Two', + thing_type=self.thing_type, + box=self.box + ) + response = self.client.get(f'/box/{self.box.id}/') + self.assertContains(response, 'Item One') + self.assertContains(response, 'Item Two') + + def test_box_detail_uses_correct_template(self): + """Box detail page should use the correct template.""" + response = self.client.get(f'/box/{self.box.id}/') + self.assertTemplateUsed(response, 'boxes/box_detail.html') + + def test_box_detail_with_image(self): + """Box detail page should show thumbnail for things with images.""" + # Create a simple 1x1 pixel PNG + image_data = ( + b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01' + b'\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00' + b'\x00\x00\x0cIDATx\x9cc\xf8\x0f\x00\x00\x01\x01\x00' + b'\x05\x18\xd8N\x00\x00\x00\x00IEND\xaeB`\x82' + ) + image = SimpleUploadedFile( + name='test.png', + content=image_data, + content_type='image/png' + ) + thing = Thing.objects.create( + name='Item With Image', + thing_type=self.thing_type, + box=self.box, + picture=image + ) + response = self.client.get(f'/box/{self.box.id}/') + self.assertContains(response, 'Item With Image') + self.assertContains(response, '/', box_detail, name='box_detail'), path('admin/', admin.site.urls), ] diff --git a/requirements.txt b/requirements.txt index 70dc548..6572697 100644 --- a/requirements.txt +++ b/requirements.txt @@ -33,5 +33,6 @@ sqlparse==0.5.3 urllib3==2.6.0 wcwidth==0.2.13 Pillow==11.1.0 +sorl-thumbnail==12.11.0 bleach==6.1.0 coverage==7.6.1