Initial commit: Django project with boxes app

- Set up Django 5.2.9 project structure
- Add boxes app with Box and BoxType models
- Box: alphanumeric ID (max 10 chars) with foreign key to BoxType
- BoxType: name and dimensions (width/height/length in mm)
- Configure admin interface for both models
- Add comprehensive test suite (14 tests)
This commit is contained in:
2025-12-28 13:04:37 +01:00
commit accefa2533
16 changed files with 487 additions and 0 deletions

0
boxes/__init__.py Normal file
View File

20
boxes/admin.py Normal file
View File

@@ -0,0 +1,20 @@
from django.contrib import admin
from .models import Box, BoxType
@admin.register(BoxType)
class BoxTypeAdmin(admin.ModelAdmin):
"""Admin configuration for BoxType model."""
list_display = ('name', 'width', 'height', 'length')
search_fields = ('name',)
@admin.register(Box)
class BoxAdmin(admin.ModelAdmin):
"""Admin configuration for Box model."""
list_display = ('id', 'box_type')
list_filter = ('box_type',)
search_fields = ('id',)

6
boxes/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class BoxesConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'boxes'

View File

@@ -0,0 +1,38 @@
# Generated by Django 5.2.9 on 2025-12-28 12:01
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='BoxType',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('width', models.PositiveIntegerField(help_text='Width in millimeters')),
('height', models.PositiveIntegerField(help_text='Height in millimeters')),
('length', models.PositiveIntegerField(help_text='Length in millimeters')),
],
options={
'ordering': ['name'],
},
),
migrations.CreateModel(
name='Box',
fields=[
('id', models.CharField(help_text='Alphanumeric identifier (max 10 characters)', max_length=10, primary_key=True, serialize=False)),
('box_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='boxes', to='boxes.boxtype')),
],
options={
'verbose_name_plural': 'boxes',
},
),
]

View File

37
boxes/models.py Normal file
View File

@@ -0,0 +1,37 @@
from django.db import models
class BoxType(models.Model):
"""A type of storage box with specific dimensions."""
name = models.CharField(max_length=255)
width = models.PositiveIntegerField(help_text='Width in millimeters')
height = models.PositiveIntegerField(help_text='Height in millimeters')
length = models.PositiveIntegerField(help_text='Length in millimeters')
class Meta:
ordering = ['name']
def __str__(self):
return self.name
class Box(models.Model):
"""A storage box in the lab."""
id = models.CharField(
max_length=10,
primary_key=True,
help_text='Alphanumeric identifier (max 10 characters)'
)
box_type = models.ForeignKey(
BoxType,
on_delete=models.PROTECT,
related_name='boxes'
)
class Meta:
verbose_name_plural = 'boxes'
def __str__(self):
return self.id

130
boxes/tests.py Normal file
View File

@@ -0,0 +1,130 @@
from django.contrib.admin.sites import AdminSite
from django.db import IntegrityError
from django.test import TestCase
from .admin import BoxAdmin, BoxTypeAdmin
from .models import Box, BoxType
class BoxTypeModelTests(TestCase):
"""Tests for the BoxType model."""
def setUp(self):
"""Set up test fixtures."""
self.box_type = BoxType.objects.create(
name='Small Box',
width=100,
height=50,
length=150
)
def test_box_type_str_returns_name(self):
"""BoxType __str__ should return the name."""
self.assertEqual(str(self.box_type), 'Small Box')
def test_box_type_creation(self):
"""BoxType should be created with correct attributes."""
self.assertEqual(self.box_type.name, 'Small Box')
self.assertEqual(self.box_type.width, 100)
self.assertEqual(self.box_type.height, 50)
self.assertEqual(self.box_type.length, 150)
def test_box_type_ordering(self):
"""BoxTypes should be ordered by name."""
BoxType.objects.create(name='Alpha Box', width=10, height=10, length=10)
BoxType.objects.create(name='Zeta Box', width=20, height=20, length=20)
box_types = list(BoxType.objects.values_list('name', flat=True))
self.assertEqual(box_types, ['Alpha Box', 'Small Box', 'Zeta Box'])
class BoxModelTests(TestCase):
"""Tests for the Box model."""
def setUp(self):
"""Set up test fixtures."""
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
)
def test_box_str_returns_id(self):
"""Box __str__ should return the box ID."""
self.assertEqual(str(self.box), 'BOX001')
def test_box_creation(self):
"""Box should be created with correct attributes."""
self.assertEqual(self.box.id, 'BOX001')
self.assertEqual(self.box.box_type, self.box_type)
def test_box_type_relationship(self):
"""Box should be accessible from BoxType via related_name."""
self.assertIn(self.box, self.box_type.boxes.all())
def test_box_id_max_length(self):
"""Box ID should accept up to 10 characters."""
box = Box.objects.create(id='ABCD123456', box_type=self.box_type)
self.assertEqual(len(box.id), 10)
def test_box_type_protect_on_delete(self):
"""Deleting a BoxType with boxes should raise IntegrityError."""
with self.assertRaises(IntegrityError):
self.box_type.delete()
def test_box_type_delete_when_no_boxes(self):
"""Deleting a BoxType without boxes should succeed."""
empty_type = BoxType.objects.create(
name='Empty Type',
width=10,
height=10,
length=10
)
empty_type_id = empty_type.id
empty_type.delete()
self.assertFalse(BoxType.objects.filter(id=empty_type_id).exists())
class BoxTypeAdminTests(TestCase):
"""Tests for the BoxType admin configuration."""
def setUp(self):
"""Set up test fixtures."""
self.site = AdminSite()
self.admin = BoxTypeAdmin(BoxType, self.site)
def test_list_display(self):
"""BoxTypeAdmin should display correct fields."""
self.assertEqual(
self.admin.list_display,
('name', 'width', 'height', 'length')
)
def test_search_fields(self):
"""BoxTypeAdmin should search by name."""
self.assertEqual(self.admin.search_fields, ('name',))
class BoxAdminTests(TestCase):
"""Tests for the Box admin configuration."""
def setUp(self):
"""Set up test fixtures."""
self.site = AdminSite()
self.admin = BoxAdmin(Box, self.site)
def test_list_display(self):
"""BoxAdmin should display correct fields."""
self.assertEqual(self.admin.list_display, ('id', 'box_type'))
def test_list_filter(self):
"""BoxAdmin should filter by box_type."""
self.assertEqual(self.admin.list_filter, ('box_type',))
def test_search_fields(self):
"""BoxAdmin should search by id."""
self.assertEqual(self.admin.search_fields, ('id',))

3
boxes/views.py Normal file
View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.